├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .gitpod.yml ├── Dockerfile ├── README.md ├── components ├── AddItemButton.js ├── AllTable.js ├── AuthRedirect.js ├── DateTimePicker.js ├── DoneTable.js ├── FolderTable.js ├── FoldersPanel.js ├── LoginForm.js ├── LogoutButton.js ├── MenuBar.js ├── RemoveFolderButton.js ├── SearchTable.js ├── SettingModal.js ├── TodayTable.js └── TodoListTable.js ├── config ├── init.sql └── mysql.js ├── docker-compose.yml ├── icons ├── logout_black_24dp.svg └── settings_black_24dp.svg ├── my.conf ├── next.config.js ├── package.json ├── pages ├── _app.js ├── all.js ├── api │ ├── authStatus.js │ ├── folders │ │ ├── [id].js │ │ └── index.js │ ├── hello.js │ ├── init.js │ ├── items │ │ ├── [id].js │ │ └── index.js │ ├── login.js │ ├── logout.js │ ├── mysql_test.js │ └── user │ │ └── [id].js ├── done.js ├── folder │ └── [folderID].js ├── home.js ├── index.js ├── login.js └── search │ └── [keywords].js ├── public ├── favicon.ico └── vercel.svg ├── readme-img ├── home.png └── login.png ├── styles ├── Home.module.css ├── globals.css └── theme.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: yarn install && yarn run build 7 | command: yarn run start 8 | 9 | 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14.18 2 | 3 | RUN mkdir -p /home/app/ && chown -R node:node /home/app 4 | WORKDIR /home/app 5 | 6 | # for product ver 7 | COPY --chown=node:node . . 8 | 9 | USER node 10 | 11 | # for product ver 12 | RUN yarn install --frozen-lockfile 13 | 14 | EXPOSE 3000 15 | CMD [ "yarn" , "dev"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CodeFactor](https://www.codefactor.io/repository/github/redfrogsss/selfremind/badge)](https://www.codefactor.io/repository/github/redfrogsss/selfremind) 2 | # SelfRemind 3 | a selfhosted To-do List Manager built with [Next.js](https://nextjs.org/), [Chakra-UI](https://chakra-ui.com/) and [MySQL](https://www.mysql.com/). 4 | 5 | ## Screenshots 6 | ![Alt text](./readme-img/login.png "Login Page") 7 | ![Alt text](./readme-img/home.png "Home Page") 8 | 9 | ## Getting Started with `docker-compose` 10 | 11 | To start with, you must install [Docker](https://www.docker.com/products/docker-desktop) and [docker-compose](https://docs.docker.com/compose/) on your computer. 12 | 13 | First, create a `docker-compose.yml` file with the following content: 14 | 15 | ```yml 16 | version: "3" 17 | services: 18 | web: 19 | build: . 20 | ports: 21 | - "3000:3000" 22 | restart: unless-stopped 23 | db: 24 | image: mysql/mysql-server:8.0 25 | restart: unless-stopped 26 | command: --default-authentication-plugin=mysql_native_password 27 | environment: 28 | MYSQL_ROOT_PASSWORD: example 29 | MYSQL_DATABASE: selfremind 30 | MYSQL_USER: selfremind 31 | MYSQL_PASSWORD: selfremind123 32 | volumes: 33 | - dbdata:/var/lib/mysql 34 | - ./my.conf:/etc/mysql/my.cnf 35 | volumes: 36 | dbdata: 37 | ``` 38 | 39 | Then, run the command to start the server: 40 | ```bash 41 | docker-compose up -d 42 | ``` 43 | 44 | Wait a minutes and open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 45 | 46 | By default, the username is `admin` and the password is `admin`. 47 | 48 | ## Learn More 49 | 50 | This is a side project built by [Jacky Fan](https://github.com/redfrogsss) - a university student in HK. 51 | 52 | The following tech is used in this project. 53 | - [Next.js](https://nextjs.org/) - the frontend and backend framework of this project. 54 | - [Chakra-UI](https://chakra-ui.com/) - the UI solutions of this project. 55 | - [MySQL](https://www.mysql.com/) - the database of this project. 56 | 57 | You can check out [my dirty code in Github](https://github.com/redfrogsss/selfremind) - your feedback are welcome! 58 | -------------------------------------------------------------------------------- /components/AddItemButton.js: -------------------------------------------------------------------------------- 1 | import { AddIcon } from "@chakra-ui/icons"; 2 | import { Button, Stack } from "@chakra-ui/react"; 3 | import { 4 | Modal, 5 | ModalOverlay, 6 | ModalContent, 7 | ModalHeader, 8 | ModalFooter, 9 | ModalBody, 10 | ModalCloseButton, 11 | } from '@chakra-ui/react' 12 | import { useDisclosure } from '@chakra-ui/react' 13 | import { 14 | FormControl, 15 | FormLabel, 16 | } from '@chakra-ui/react' 17 | import { Input } from '@chakra-ui/react' 18 | import { Textarea } from '@chakra-ui/react' 19 | import DateTimePicker from "./DateTimePicker"; 20 | import { Select } from '@chakra-ui/react' 21 | import { useToast } from '@chakra-ui/react' 22 | import { useEffect, useState } from "react"; 23 | import axios from "axios"; 24 | import { useCookies } from 'react-cookie'; 25 | import moment from 'moment' 26 | import { useRouter } from 'next/router' 27 | 28 | export default function AddItemButton() { 29 | const router = useRouter() 30 | 31 | const [cookies, setCookie, removeCookie] = useCookies(['userID']); 32 | const { isOpen, onOpen, onClose } = useDisclosure() 33 | const [datetime, setDatetime] = useState(new Date()); 34 | const toast = useToast(); 35 | const [folderData, setFolderData] = useState([]); 36 | 37 | // fetch folder data 38 | useEffect(() => { 39 | axios.get("/api/folders?userID=" + cookies.userID) 40 | .then((res) => { 41 | setFolderData(res.data.result); 42 | }) 43 | .catch((err) => { 44 | console.log(err); 45 | }); 46 | }, []); 47 | 48 | const SuccessToast = () => 49 | toast({ 50 | description: 'Item created successfully.', 51 | status: 'success', 52 | duration: 9000, 53 | isClosable: true, 54 | }); 55 | 56 | const FailToast = () => 57 | toast({ 58 | description: 'Item created failed.', 59 | status: 'error', 60 | duration: 9000, 61 | isClosable: true, 62 | }); 63 | 64 | const submitHandler = (e) => { 65 | e.preventDefault(); 66 | 67 | var data = { 68 | userID: cookies.userID, 69 | name: e.target.name.value, 70 | description: e.target.description.value, 71 | datetime: datetime, 72 | reminder: e.target.reminder.value, 73 | repeat: e.target.repeat.value, 74 | folder: e.target.folder.value 75 | } 76 | 77 | console.log(data); 78 | axios.post("/api/items", data).then((res) => { 79 | onClose(); 80 | SuccessToast(); 81 | router.push("/folder/" + data.folder); 82 | }).catch((err) => { 83 | FailToast(); 84 | console.error(err); 85 | }); 86 | } 87 | 88 | const datetimeHandler = (value) => { 89 | setDatetime(moment(value)); 90 | } 91 | 92 | const printFolderOptions = () => { 93 | return folderData.map((value) => { 94 | return 95 | }); 96 | } 97 | 98 | return ( 99 | <> 100 | 107 | 108 | 109 |
110 | 111 | 112 | Add Item 113 | 114 | 115 | 116 | 117 | 118 | Name 119 | 120 | 121 | 122 | Description 123 |