From 1b436d2dc6e125e63e036ee067d3faa284c24da3 Mon Sep 17 00:00:00 2001 From: sosokker Date: Thu, 9 Nov 2023 23:52:33 +0700 Subject: [PATCH 01/18] Add RecurrenceTaskViewSet --- backend/tasks/tasks/views.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/tasks/tasks/views.py b/backend/tasks/tasks/views.py index d884bbe..16f014c 100644 --- a/backend/tasks/tasks/views.py +++ b/backend/tasks/tasks/views.py @@ -1,6 +1,6 @@ from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated -from tasks.models import Todo +from tasks.models import Todo, RecurrenceTask from .serializers import TaskCreateSerializer, TaskGeneralSerializer @@ -13,4 +13,10 @@ class TodoViewSet(viewsets.ModelViewSet): # Can't add ManytoMany at creation time (Tags) if self.action == 'create': return TaskCreateSerializer - return TaskGeneralSerializer \ No newline at end of file + return TaskGeneralSerializer + + +class RecurrenceTaskViewSet(viewsets.ModelViewSet): + queryset = Todo.objects.all() + serializer_class = TaskGeneralSerializer + permission_classes = [IsAuthenticated] \ No newline at end of file From 9a7ffe7aa06399e7a4f72106d9792fa9e3214dca Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 13 Nov 2023 22:32:40 +0700 Subject: [PATCH 02/18] Fix Overflow in calendar --- frontend/src/App.jsx | 4 +- frontend/src/components/calendar/calendar.jsx | 49 +++++++++-------- frontend/src/components/calendar/index.css | 55 ------------------- .../{ => navigations}/IconSideNav.jsx | 0 .../{Nav => navigations}/Navbar.jsx | 4 +- 5 files changed, 29 insertions(+), 83 deletions(-) delete mode 100644 frontend/src/components/calendar/index.css rename frontend/src/components/{ => navigations}/IconSideNav.jsx (100%) rename frontend/src/components/{Nav => navigations}/Navbar.jsx (97%) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index d2bcd8d..78f312c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,12 +4,12 @@ import { Route, Routes, useLocation } from "react-router-dom"; import TestAuth from "./components/testAuth"; import LoginPage from "./components/authentication/LoginPage"; import SignUpPage from "./components/authentication/SignUpPage"; -import NavBar from "./components/Nav/Navbar"; +import NavBar from "./components/navigations/Navbar"; import Home from "./components/Home"; import ProfileUpdate from "./components/ProfileUpdatePage"; import Calendar from "./components/calendar/calendar"; import KanbanBoard from "./components/kanbanBoard/kanbanBoard"; -import IconSideNav from "./components/IconSideNav"; +import IconSideNav from "./components/navigations/IconSideNav"; import Eisenhower from "./components/eisenhowerMatrix/Eisenhower"; const App = () => { diff --git a/frontend/src/components/calendar/calendar.jsx b/frontend/src/components/calendar/calendar.jsx index 443d4bc..6258da9 100644 --- a/frontend/src/components/calendar/calendar.jsx +++ b/frontend/src/components/calendar/calendar.jsx @@ -5,7 +5,6 @@ import dayGridPlugin from "@fullcalendar/daygrid"; import timeGridPlugin from "@fullcalendar/timegrid"; import interactionPlugin from "@fullcalendar/interaction"; import { getEvents, createEventId } from "./TaskDataHandler"; -import './index.css' export default class Calendar extends React.Component { state = { @@ -15,9 +14,9 @@ export default class Calendar extends React.Component { render() { return ( -
+
{this.renderSidebar()} -
+
@@ -49,23 +43,30 @@ export default class Calendar extends React.Component { renderSidebar() { return ( -
-
-

Instructions

-
    +
    +
    +

    Instructions

    +
    • Select dates and you will be prompted to create a new event
    • Drag, drop, and resize events
    • Click an event to delete it
    -
    -
    +
    From bac1f96ad8f941356cfcaf5b01f8e64deb517a66 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 13 Nov 2023 23:46:52 +0700 Subject: [PATCH 03/18] Add Redirect Router if not authen --- frontend/src/App.jsx | 41 +++++++++++-------- frontend/src/PrivateRoute.jsx | 10 +++++ frontend/src/api/configs/AxiosConfig.jsx | 3 +- .../authentication/IsAuthenticated.jsx | 19 --------- .../src/components/navigations/Navbar.jsx | 2 +- .../hooks/authentication/IsAuthenticated.jsx | 25 +++++++++++ frontend/src/main.jsx | 14 ++++--- 7 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 frontend/src/PrivateRoute.jsx delete mode 100644 frontend/src/components/authentication/IsAuthenticated.jsx create mode 100644 frontend/src/hooks/authentication/IsAuthenticated.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 78f312c..6a3468f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,6 +11,7 @@ import Calendar from "./components/calendar/calendar"; import KanbanBoard from "./components/kanbanBoard/kanbanBoard"; import IconSideNav from "./components/navigations/IconSideNav"; import Eisenhower from "./components/eisenhowerMatrix/Eisenhower"; +import PrivateRoute from "./PrivateRoute"; const App = () => { const location = useLocation(); @@ -18,24 +19,32 @@ const App = () => { const isLoginPageOrSignUpPage = prevention.some(_ => location.pathname.includes(_)); return ( -
    - {!isLoginPageOrSignUpPage && } -
    - -
    - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - -
    +
    + {!isLoginPageOrSignUpPage && } +
    + +
    + + } /> + }> + } /> + + } /> + }> + } /> + + }> + } /> + + }> + } /> + + } /> + } /> +
    +
    ); }; diff --git a/frontend/src/PrivateRoute.jsx b/frontend/src/PrivateRoute.jsx new file mode 100644 index 0000000..936a7d9 --- /dev/null +++ b/frontend/src/PrivateRoute.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; +import IsAuthenticated from './hooks/authentication/IsAuthenticated'; + +const PrivateRoute = () => { + const auth = IsAuthenticated(); + return auth ? : ; +} + +export default PrivateRoute; \ No newline at end of file diff --git a/frontend/src/api/configs/AxiosConfig.jsx b/frontend/src/api/configs/AxiosConfig.jsx index 80015ac..a6469ae 100644 --- a/frontend/src/api/configs/AxiosConfig.jsx +++ b/frontend/src/api/configs/AxiosConfig.jsx @@ -1,4 +1,5 @@ import axios from 'axios'; +import { redirect } from 'react-router-dom'; const axiosInstance = axios.create({ baseURL: 'http://127.0.0.1:8000/api/', @@ -16,7 +17,7 @@ axiosInstance.interceptors.response.use( error => { const originalRequest = error.config; const refresh_token = localStorage.getItem('refresh_token'); - + // Check if the error is due to 401 and a refresh token is available if (error.response.status === 401 && error.response.statusText === "Unauthorized" && refresh_token !== "undefined") { return axiosInstance diff --git a/frontend/src/components/authentication/IsAuthenticated.jsx b/frontend/src/components/authentication/IsAuthenticated.jsx deleted file mode 100644 index 48322de..0000000 --- a/frontend/src/components/authentication/IsAuthenticated.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useState, useEffect } from 'react'; - -function IsAuthenticated() { - const [isAuthenticated, setIsAuthenticated] = useState(false); - - useEffect(() => { - const access_token = localStorage.getItem('access_token'); - - if (access_token) { - setIsAuthenticated(true); - } else { - setIsAuthenticated(false); - } - }, []); - - return isAuthenticated; -} - -export default IsAuthenticated; \ No newline at end of file diff --git a/frontend/src/components/navigations/Navbar.jsx b/frontend/src/components/navigations/Navbar.jsx index 75cd8a7..9328e1c 100644 --- a/frontend/src/components/navigations/Navbar.jsx +++ b/frontend/src/components/navigations/Navbar.jsx @@ -1,6 +1,6 @@ import * as React from "react"; import { useNavigate } from "react-router-dom"; -import IsAuthenticated from "../authentication/IsAuthenticated"; +import IsAuthenticated from "../../hooks/authentication/IsAuthenticated"; import axiosapi from "../../api/AuthenticationApi"; const settings = { diff --git a/frontend/src/hooks/authentication/IsAuthenticated.jsx b/frontend/src/hooks/authentication/IsAuthenticated.jsx new file mode 100644 index 0000000..a132fb2 --- /dev/null +++ b/frontend/src/hooks/authentication/IsAuthenticated.jsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; + +function IsAuthenticated() { + const [isAuthenticated, setIsAuthenticated] = useState(() => { + const access_token = localStorage.getItem('access_token'); + return !!access_token; + }); + + useEffect(() => { + const handleTokenChange = () => { + const newAccessToken = localStorage.getItem('access_token'); + setIsAuthenticated(!!newAccessToken); + }; + + window.addEventListener('storage', handleTokenChange); + + return () => { + window.removeEventListener('storage', handleTokenChange); + }; + }, []); + + return isAuthenticated; +} + +export default IsAuthenticated; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index f5430b9..262782a 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,15 +1,17 @@ -import React from "react"; +import React, { Fragment } from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; -import { GoogleOAuthProvider} from '@react-oauth/google'; -import { BrowserRouter } from 'react-router-dom'; +import { GoogleOAuthProvider } from "@react-oauth/google"; +import { BrowserRouter } from "react-router-dom"; -const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID +const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID; ReactDOM.createRoot(document.getElementById("root")).render( - + + + -); \ No newline at end of file +); From 038a0c84b77277b63eda0056bc999b6964abb1c9 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 13 Nov 2023 23:51:17 +0700 Subject: [PATCH 04/18] Format code with Prettier --- frontend/src/App.css | 2 +- frontend/src/PrivateRoute.jsx | 14 +- frontend/src/api/UserProfileApi.jsx | 12 +- frontend/src/api/configs/AxiosConfig.jsx | 62 ++++---- .../EisenhowerMatrix/Eisenhower.jsx | 10 +- frontend/src/components/Home.jsx | 2 +- frontend/src/components/ProfileUpdatePage.jsx | 31 ++-- .../components/authentication/SignUpPage.jsx | 140 +++++++++--------- .../authentication/refreshAcessToken.jsx | 12 +- .../components/calendar/TaskDataHandler.jsx | 38 ++--- frontend/src/components/calendar/calendar.jsx | 8 +- frontend/src/components/icons/plusIcon.jsx | 11 +- frontend/src/components/icons/trashIcon.jsx | 32 ++-- .../kanbanBoard/columnContainer.jsx | 52 ++----- .../components/kanbanBoard/kanbanBoard.jsx | 67 +++------ .../src/components/kanbanBoard/taskCard.jsx | 28 +--- .../src/components/navigations/Navbar.jsx | 6 +- frontend/src/components/signup.jsx | 77 +++++----- frontend/src/components/testAuth.jsx | 67 +++++---- .../hooks/authentication/IsAuthenticated.jsx | 10 +- frontend/src/index.css | 14 +- 21 files changed, 310 insertions(+), 385 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index eaac616..48931c9 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -43,4 +43,4 @@ to { transform: rotate(360deg); } -} \ No newline at end of file +} diff --git a/frontend/src/PrivateRoute.jsx b/frontend/src/PrivateRoute.jsx index 936a7d9..600f7d4 100644 --- a/frontend/src/PrivateRoute.jsx +++ b/frontend/src/PrivateRoute.jsx @@ -1,10 +1,10 @@ -import React from 'react'; -import { Navigate, Outlet } from 'react-router-dom'; -import IsAuthenticated from './hooks/authentication/IsAuthenticated'; +import React from "react"; +import { Navigate, Outlet } from "react-router-dom"; +import IsAuthenticated from "./hooks/authentication/IsAuthenticated"; const PrivateRoute = () => { - const auth = IsAuthenticated(); - return auth ? : ; -} + const auth = IsAuthenticated(); + return auth ? : ; +}; -export default PrivateRoute; \ No newline at end of file +export default PrivateRoute; diff --git a/frontend/src/api/UserProfileApi.jsx b/frontend/src/api/UserProfileApi.jsx index ca80fd1..e7a1b17 100644 --- a/frontend/src/api/UserProfileApi.jsx +++ b/frontend/src/api/UserProfileApi.jsx @@ -1,11 +1,11 @@ -import axios from 'axios'; +import axios from "axios"; -const ApiUpdateUserProfile = async (formData) => { +const ApiUpdateUserProfile = async formData => { try { - const response = await axios.post('http://127.0.1:8000/api/user/update/', formData, { + const response = await axios.post("http://127.0.1:8000/api/user/update/", formData, { headers: { - 'Authorization': "Bearer " + localStorage.getItem('access_token'), - 'Content-Type': 'multipart/form-data', + Authorization: "Bearer " + localStorage.getItem("access_token"), + "Content-Type": "multipart/form-data", }, }); @@ -13,7 +13,7 @@ const ApiUpdateUserProfile = async (formData) => { return response.data; } catch (error) { - console.error('Error updating user profile:', error); + console.error("Error updating user profile:", error); throw error; } }; diff --git a/frontend/src/api/configs/AxiosConfig.jsx b/frontend/src/api/configs/AxiosConfig.jsx index a6469ae..b0410d1 100644 --- a/frontend/src/api/configs/AxiosConfig.jsx +++ b/frontend/src/api/configs/AxiosConfig.jsx @@ -1,43 +1,45 @@ -import axios from 'axios'; -import { redirect } from 'react-router-dom'; +import axios from "axios"; +import { redirect } from "react-router-dom"; const axiosInstance = axios.create({ - baseURL: 'http://127.0.0.1:8000/api/', - timeout: 5000, - headers: { - 'Authorization': "Bearer " + localStorage.getItem('access_token'), - 'Content-Type': 'application/json', - 'accept': 'application/json', - }, + baseURL: "http://127.0.0.1:8000/api/", + timeout: 5000, + headers: { + Authorization: "Bearer " + localStorage.getItem("access_token"), + "Content-Type": "application/json", + accept: "application/json", + }, }); // handling token refresh on 401 Unauthorized errors axiosInstance.interceptors.response.use( - response => response, - error => { - const originalRequest = error.config; - const refresh_token = localStorage.getItem('refresh_token'); - - // Check if the error is due to 401 and a refresh token is available - if (error.response.status === 401 && error.response.statusText === "Unauthorized" && refresh_token !== "undefined") { - return axiosInstance - .post('/token/refresh/', { refresh: refresh_token }) - .then((response) => { + response => response, + error => { + const originalRequest = error.config; + const refresh_token = localStorage.getItem("refresh_token"); - localStorage.setItem('access_token', response.data.access); + // Check if the error is due to 401 and a refresh token is available + if ( + error.response.status === 401 && + error.response.statusText === "Unauthorized" && + refresh_token !== "undefined" + ) { + return axiosInstance + .post("/token/refresh/", { refresh: refresh_token }) + .then(response => { + localStorage.setItem("access_token", response.data.access); - axiosInstance.defaults.headers['Authorization'] = "Bearer " + response.data.access; - originalRequest.headers['Authorization'] = "Bearer " + response.data.access; + axiosInstance.defaults.headers["Authorization"] = "Bearer " + response.data.access; + originalRequest.headers["Authorization"] = "Bearer " + response.data.access; - return axiosInstance(originalRequest); - }) - .catch(err => { - console.log('Interceptors error: ', err); - }); - } - return Promise.reject(error); + return axiosInstance(originalRequest); + }) + .catch(err => { + console.log("Interceptors error: ", err); + }); } + return Promise.reject(error); + } ); - export default axiosInstance; diff --git a/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx b/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx index 7800fa9..c300236 100644 --- a/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx +++ b/frontend/src/components/EisenhowerMatrix/Eisenhower.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; function EachBlog({ name, colorCode }) { return ( @@ -6,18 +6,16 @@ function EachBlog({ name, colorCode }) {
    {name}
    -
    - Content goes here -
    +
    Content goes here
    ); } function Eisenhower() { return ( -
    +

    The Eisenhower Matrix

    -
    +
    diff --git a/frontend/src/components/Home.jsx b/frontend/src/components/Home.jsx index 3089df5..7fa7680 100644 --- a/frontend/src/components/Home.jsx +++ b/frontend/src/components/Home.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; function HomePage() { return ( diff --git a/frontend/src/components/ProfileUpdatePage.jsx b/frontend/src/components/ProfileUpdatePage.jsx index 06a0213..6985851 100644 --- a/frontend/src/components/ProfileUpdatePage.jsx +++ b/frontend/src/components/ProfileUpdatePage.jsx @@ -1,12 +1,12 @@ -import React, { useState, useRef } from 'react'; -import { ApiUpdateUserProfile } from '../api/UserProfileApi'; +import React, { useState, useRef } from "react"; +import { ApiUpdateUserProfile } from "../api/UserProfileApi"; function ProfileUpdate() { const [file, setFile] = useState(null); - const [username, setUsername] = useState(''); - const [fullName, setFullName] = useState(''); - const [about, setAbout] = useState(''); - const defaultImage = 'https://i1.sndcdn.com/artworks-cTz48e4f1lxn5Ozp-L3hopw-t500x500.jpg'; + const [username, setUsername] = useState(""); + const [fullName, setFullName] = useState(""); + const [about, setAbout] = useState(""); + const defaultImage = "https://i1.sndcdn.com/artworks-cTz48e4f1lxn5Ozp-L3hopw-t500x500.jpg"; const fileInputRef = useRef(null); const handleImageUpload = () => { @@ -15,7 +15,7 @@ function ProfileUpdate() { } }; - const handleFileChange = (e) => { + const handleFileChange = e => { const selectedFile = e.target.files[0]; if (selectedFile) { setFile(selectedFile); @@ -24,9 +24,9 @@ function ProfileUpdate() { const handleSave = () => { const formData = new FormData(); - formData.append('profile_pic', file); - formData.append('first_name', username); - formData.append('about', about); + formData.append("profile_pic", file); + formData.append("first_name", username); + formData.append("about", about); ApiUpdateUserProfile(formData); }; @@ -45,10 +45,7 @@ function ProfileUpdate() { ref={fileInputRef} /> -
    +
    {file ? ( Profile ) : ( @@ -69,7 +66,7 @@ function ProfileUpdate() { placeholder="Enter your username" className="input w-full" value={username} - onChange={(e) => setUsername(e.target.value)} + onChange={e => setUsername(e.target.value)} />
    @@ -81,7 +78,7 @@ function ProfileUpdate() { placeholder="Enter your full name" className="input w-full" value={fullName} - onChange={(e) => setFullName(e.target.value)} + onChange={e => setFullName(e.target.value)} />
    @@ -92,7 +89,7 @@ function ProfileUpdate() { placeholder="Tell us about yourself" className="textarea w-full h-32" value={about} - onChange={(e) => setAbout(e.target.value)} + onChange={e => setAbout(e.target.value)} />
    diff --git a/frontend/src/components/authentication/SignUpPage.jsx b/frontend/src/components/authentication/SignUpPage.jsx index 2712f97..28eb1c2 100644 --- a/frontend/src/components/authentication/SignUpPage.jsx +++ b/frontend/src/components/authentication/SignUpPage.jsx @@ -1,31 +1,30 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import axiosapi from '../../api/AuthenticationApi'; - -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; -import CssBaseline from '@mui/material/CssBaseline'; -import TextField from '@mui/material/TextField'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import Checkbox from '@mui/material/Checkbox'; -import Link from '@mui/material/Link'; -import Grid from '@mui/material/Grid'; -import Box from '@mui/material/Box'; -import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; -import Typography from '@mui/material/Typography'; -import Container from '@mui/material/Container'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axiosapi from "../../api/AuthenticationApi"; +import Avatar from "@mui/material/Avatar"; +import Button from "@mui/material/Button"; +import CssBaseline from "@mui/material/CssBaseline"; +import TextField from "@mui/material/TextField"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Checkbox from "@mui/material/Checkbox"; +import Link from "@mui/material/Link"; +import Grid from "@mui/material/Grid"; +import Box from "@mui/material/Box"; +import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; +import Typography from "@mui/material/Typography"; +import Container from "@mui/material/Container"; +import { createTheme, ThemeProvider } from "@mui/material/styles"; function Copyright(props) { return ( - {'Copyright © '} + {"Copyright © "} TurTask - {' '} + {" "} {new Date().getFullYear()} - {'.'} + {"."} ); } @@ -33,37 +32,36 @@ function Copyright(props) { const defaultTheme = createTheme(); export default function SignUp() { + const Navigate = useNavigate(); - const Navigate = useNavigate(); + const [formData, setFormData] = useState({ + email: "", + username: "", + password: "", + }); + const [error, setError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); - const [formData, setFormData] = useState({ - email: '', - username: '', - password: '', - }); - const [error, setError] = useState(null); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleSubmit = async (e) => { - e.preventDefault(); - setIsSubmitting(true); - setError(null); - - try { - axiosapi.createUser(formData); - } catch (error) { - console.error('Error creating user:', error); - setError('Registration failed. Please try again.'); - } finally { - setIsSubmitting(false); - } - Navigate('/login'); - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); - }; + const handleSubmit = async e => { + e.preventDefault(); + setIsSubmitting(true); + setError(null); + + try { + axiosapi.createUser(formData); + } catch (error) { + console.error("Error creating user:", error); + setError("Registration failed. Please try again."); + } finally { + setIsSubmitting(false); + } + Navigate("/login"); + }; + + const handleChange = e => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; return ( @@ -71,13 +69,12 @@ export default function SignUp() { - + marginTop: 8, + display: "flex", + flexDirection: "column", + alignItems: "center", + }}> + @@ -85,17 +82,17 @@ export default function SignUp() { - - - + + + - @@ -148,4 +140,4 @@ export default function SignUp() { ); -} \ No newline at end of file +} diff --git a/frontend/src/components/authentication/refreshAcessToken.jsx b/frontend/src/components/authentication/refreshAcessToken.jsx index 89204d5..11a74fa 100644 --- a/frontend/src/components/authentication/refreshAcessToken.jsx +++ b/frontend/src/components/authentication/refreshAcessToken.jsx @@ -1,8 +1,8 @@ -import axios from 'axios'; +import axios from "axios"; async function refreshAccessToken() { - const refresh_token = localStorage.getItem('refresh_token'); - const access_token = localStorage.getItem('access_token'); + const refresh_token = localStorage.getItem("refresh_token"); + const access_token = localStorage.getItem("access_token"); if (access_token) { return true; @@ -12,7 +12,7 @@ async function refreshAccessToken() { return false; } - const refreshUrl = 'http://127.0.0.1:8000/api/token/refresh/'; + const refreshUrl = "http://127.0.0.1:8000/api/token/refresh/"; try { const response = await axios.post(refreshUrl, { refresh: refresh_token }); @@ -22,8 +22,8 @@ async function refreshAccessToken() { const newAccessToken = response.data.access; const newRefreshToken = response.data.refresh; - localStorage.setItem('access_token', newAccessToken); - localStorage.setItem('refresh_token', newRefreshToken); + localStorage.setItem("access_token", newAccessToken); + localStorage.setItem("refresh_token", newRefreshToken); return true; } else { diff --git a/frontend/src/components/calendar/TaskDataHandler.jsx b/frontend/src/components/calendar/TaskDataHandler.jsx index 3c123e9..933cb42 100644 --- a/frontend/src/components/calendar/TaskDataHandler.jsx +++ b/frontend/src/components/calendar/TaskDataHandler.jsx @@ -1,6 +1,6 @@ -import { fetchTodoTasks } from '../../api/TaskApi'; +import { fetchTodoTasks } from "../../api/TaskApi"; -let eventGuid = 0 +let eventGuid = 0; // function getDateAndTime(dateString) { // const dateObject = new Date(dateString); @@ -18,25 +18,25 @@ let eventGuid = 0 // return dateFormatted + timeFormatted; // } -const mapResponseToEvents = (response) => { - return response.map(item => ({ - id: createEventId(), - title: item.title, - start: item.start_event, - end: item.end_event, - })); -} +const mapResponseToEvents = response => { + return response.map(item => ({ + id: createEventId(), + title: item.title, + start: item.start_event, + end: item.end_event, + })); +}; export async function getEvents() { - try { - const response = await fetchTodoTasks(); - return mapResponseToEvents(response); - } catch (error) { - console.error(error); - return []; - } + try { + const response = await fetchTodoTasks(); + return mapResponseToEvents(response); + } catch (error) { + console.error(error); + return []; + } } export function createEventId() { - return String(eventGuid++); -} \ No newline at end of file + return String(eventGuid++); +} diff --git a/frontend/src/components/calendar/calendar.jsx b/frontend/src/components/calendar/calendar.jsx index 6258da9..f1f63e5 100644 --- a/frontend/src/components/calendar/calendar.jsx +++ b/frontend/src/components/calendar/calendar.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState } from "react"; import { formatDate } from "@fullcalendar/core"; import FullCalendar from "@fullcalendar/react"; import dayGridPlugin from "@fullcalendar/daygrid"; @@ -79,7 +79,7 @@ export default class Calendar extends React.Component { }); }; - handleDateSelect = (selectInfo) => { + handleDateSelect = selectInfo => { let title = prompt("Please enter a new title for your event"); let calendarApi = selectInfo.view.calendar; @@ -96,13 +96,13 @@ export default class Calendar extends React.Component { } }; - handleEventClick = (clickInfo) => { + handleEventClick = clickInfo => { if (confirm(`Are you sure you want to delete the event '${clickInfo.event.title}'`)) { clickInfo.event.remove(); } }; - handleEvents = (events) => { + handleEvents = events => { this.setState({ currentEvents: events, }); diff --git a/frontend/src/components/icons/plusIcon.jsx b/frontend/src/components/icons/plusIcon.jsx index 080d990..7363a7b 100644 --- a/frontend/src/components/icons/plusIcon.jsx +++ b/frontend/src/components/icons/plusIcon.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; function PlusIcon() { return ( @@ -8,13 +8,8 @@ function PlusIcon() { viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" - className="w-6 h-6" - > - + className="w-6 h-6"> + ); } diff --git a/frontend/src/components/icons/trashIcon.jsx b/frontend/src/components/icons/trashIcon.jsx index f0be9fc..c7721fd 100644 --- a/frontend/src/components/icons/trashIcon.jsx +++ b/frontend/src/components/icons/trashIcon.jsx @@ -1,22 +1,20 @@ -import React from 'react'; +import React from "react"; function TrashIcon() { - return ( - React.createElement( - "svg", - { - xmlns: "http://www.w3.org/2000/svg", - fill: "none", - viewBox: "0 0 24 24", - strokeWidth: 1.5, - className: "w-6 h-6" - }, - React.createElement("path", { - strokeLinecap: "round", - strokeLinejoin: "round", - d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" - }) - ) + return React.createElement( + "svg", + { + xmlns: "http://www.w3.org/2000/svg", + fill: "none", + viewBox: "0 0 24 24", + strokeWidth: 1.5, + className: "w-6 h-6", + }, + React.createElement("path", { + strokeLinecap: "round", + strokeLinejoin: "round", + d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0", + }) ); } diff --git a/frontend/src/components/kanbanBoard/columnContainer.jsx b/frontend/src/components/kanbanBoard/columnContainer.jsx index 5ff2333..102e424 100644 --- a/frontend/src/components/kanbanBoard/columnContainer.jsx +++ b/frontend/src/components/kanbanBoard/columnContainer.jsx @@ -5,29 +5,14 @@ import { useMemo, useState } from "react"; import PlusIcon from "../icons/plusIcon"; import TaskCard from "./taskCard"; -function ColumnContainer({ - column, - deleteColumn, - updateColumn, - createTask, - tasks, - deleteTask, - updateTask, -}) { +function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks, deleteTask, updateTask }) { const [editMode, setEditMode] = useState(false); const tasksIds = useMemo(() => { - return tasks.map((task) => task.id); + return tasks.map(task => task.id); }, [tasks]); - const { - setNodeRef, - attributes, - listeners, - transform, - transition, - isDragging, - } = useSortable({ + const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({ id: column.id, data: { type: "Column", @@ -57,8 +42,7 @@ function ColumnContainer({ rounded-md flex flex-col - " - >
    + ">
    ); } @@ -74,8 +58,7 @@ function ColumnContainer({ rounded-md flex flex-col - " - > + "> {/* Column title */}
    + ">
    + "> 0
    {!editMode && column.title} @@ -119,12 +100,12 @@ function ColumnContainer({ updateColumn(column.id, e.target.value)} + onChange={e => updateColumn(column.id, e.target.value)} autoFocus onBlur={() => { setEditMode(false); }} - onKeyDown={(e) => { + onKeyDown={e => { if (e.key !== "Enter") return; setEditMode(false); }} @@ -142,8 +123,7 @@ function ColumnContainer({ rounded px-1 py-2 - " - > + ">
    @@ -151,13 +131,8 @@ function ColumnContainer({ {/* Column task container */}
    - {tasks.map((task) => ( - + {tasks.map(task => ( + ))}
    @@ -166,8 +141,7 @@ function ColumnContainer({ className="flex gap-2 items-center border-columnBackgroundColor border-2 rounded-md p-4 border-x-columnBackgroundColor hover:bg-mainBackgroundColor hover:text-rose-500 active:bg-black" onClick={() => { createTask(column.id); - }} - > + }}> Add task diff --git a/frontend/src/components/kanbanBoard/kanbanBoard.jsx b/frontend/src/components/kanbanBoard/kanbanBoard.jsx index 1ec76b5..743d8a9 100644 --- a/frontend/src/components/kanbanBoard/kanbanBoard.jsx +++ b/frontend/src/components/kanbanBoard/kanbanBoard.jsx @@ -1,13 +1,7 @@ -import PlusIcon from "../icons/plusIcon" +import PlusIcon from "../icons/plusIcon"; import { useMemo, useState } from "react"; import ColumnContainer from "./columnContainer"; -import { - DndContext, - DragOverlay, - PointerSensor, - useSensor, - useSensors, -} from "@dnd-kit/core"; +import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import { SortableContext, arrayMove } from "@dnd-kit/sortable"; import { createPortal } from "react-dom"; import TaskCard from "./taskCard"; @@ -98,7 +92,7 @@ const defaultTasks = [ function KanbanBoard() { const [columns, setColumns] = useState(defaultCols); - const columnsId = useMemo(() => columns.map((col) => col.id), [columns]); + const columnsId = useMemo(() => columns.map(col => col.id), [columns]); const [tasks, setTasks] = useState(defaultTasks); @@ -123,18 +117,12 @@ function KanbanBoard() { items-center overflow-x-auto overflow-y-hidden - " - > - + "> +
    - {columns.map((col) => ( + {columns.map(col => ( task.columnId === col.id)} + tasks={tasks.filter(task => task.columnId === col.id)} /> ))} @@ -166,8 +154,7 @@ function KanbanBoard() { hover:ring-2 flex gap-2 - " - > + "> Add Column @@ -183,18 +170,10 @@ function KanbanBoard() { createTask={createTask} deleteTask={deleteTask} updateTask={updateTask} - tasks={tasks.filter( - (task) => task.columnId === activeColumn.id - )} - /> - )} - {activeTask && ( - task.columnId === activeColumn.id)} /> )} + {activeTask && } , document.body )} @@ -213,12 +192,12 @@ function KanbanBoard() { } function deleteTask(id) { - const newTasks = tasks.filter((task) => task.id !== id); + const newTasks = tasks.filter(task => task.id !== id); setTasks(newTasks); } function updateTask(id, content) { - const newTasks = tasks.map((task) => { + const newTasks = tasks.map(task => { if (task.id !== id) return task; return { ...task, content }; }); @@ -236,15 +215,15 @@ function KanbanBoard() { } function deleteColumn(id) { - const filteredColumns = columns.filter((col) => col.id !== id); + const filteredColumns = columns.filter(col => col.id !== id); setColumns(filteredColumns); - const newTasks = tasks.filter((t) => t.columnId !== id); + const newTasks = tasks.filter(t => t.columnId !== id); setTasks(newTasks); } function updateColumn(id, title) { - const newColumns = columns.map((col) => { + const newColumns = columns.map(col => { if (col.id !== id) return col; return { ...col, title }; }); @@ -279,10 +258,10 @@ function KanbanBoard() { const isActiveAColumn = active.data.current?.type === "Column"; if (!isActiveAColumn) return; - setColumns((columns) => { - const activeColumnIndex = columns.findIndex((col) => col.id === activeId); + setColumns(columns => { + const activeColumnIndex = columns.findIndex(col => col.id === activeId); - const overColumnIndex = columns.findIndex((col) => col.id === overId); + const overColumnIndex = columns.findIndex(col => col.id === overId); return arrayMove(columns, activeColumnIndex, overColumnIndex); }); @@ -303,9 +282,9 @@ function KanbanBoard() { if (!isActiveATask) return; if (isActiveATask && isOverATask) { - setTasks((tasks) => { - const activeIndex = tasks.findIndex((t) => t.id === activeId); - const overIndex = tasks.findIndex((t) => t.id === overId); + setTasks(tasks => { + const activeIndex = tasks.findIndex(t => t.id === activeId); + const overIndex = tasks.findIndex(t => t.id === overId); if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) { tasks[activeIndex].columnId = tasks[overIndex].columnId; @@ -319,8 +298,8 @@ function KanbanBoard() { const isOverAColumn = over.data.current?.type === "Column"; if (isActiveATask && isOverAColumn) { - setTasks((tasks) => { - const activeIndex = tasks.findIndex((t) => t.id === activeId); + setTasks(tasks => { + const activeIndex = tasks.findIndex(t => t.id === activeId); tasks[activeIndex].columnId = overId; return arrayMove(tasks, activeIndex, activeIndex); diff --git a/frontend/src/components/kanbanBoard/taskCard.jsx b/frontend/src/components/kanbanBoard/taskCard.jsx index c6fb391..9dcafa7 100644 --- a/frontend/src/components/kanbanBoard/taskCard.jsx +++ b/frontend/src/components/kanbanBoard/taskCard.jsx @@ -7,14 +7,7 @@ function TaskCard({ task, deleteTask, updateTask }) { const [mouseIsOver, setMouseIsOver] = useState(false); const [editMode, setEditMode] = useState(true); - const { - setNodeRef, - attributes, - listeners, - transform, - transition, - isDragging, - } = useSortable({ + const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({ id: task.id, data: { type: "Task", @@ -29,7 +22,7 @@ function TaskCard({ task, deleteTask, updateTask }) { }; const toggleEditMode = () => { - setEditMode((prev) => !prev); + setEditMode(prev => !prev); setMouseIsOver(false); }; @@ -53,8 +46,7 @@ function TaskCard({ task, deleteTask, updateTask }) { style={style} {...attributes} {...listeners} - className="bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl hover:ring-2 hover:ring-inset hover:ring-rose-500 cursor-grab relative" - > + className="bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl hover:ring-2 hover:ring-inset hover:ring-rose-500 cursor-grab relative"> +
    +
    + +
    +
    +
    +
    +

    Overall Statistics

    +
    +
    +
    +
    +
    +
    +
    +

    Achievements

    +
    +
    +
    +
    +
    +
    +
    +

    Friends

    +
    +
    +
    +
    +
    + + + + {/* Modal */} + +
    +
    + + + +
    +
    +
    + ); +} +export default ProfileUpdatePage; From 02c51a7defa0e9d48a0c6b706060ea138ede3aa8 Mon Sep 17 00:00:00 2001 From: sosokker Date: Thu, 16 Nov 2023 22:20:18 +0700 Subject: [PATCH 10/18] Use react-icons instead of svg icon --- frontend/src/components/icons/plusIcon.jsx | 17 --------------- frontend/src/components/icons/trashIcon.jsx | 21 ------------------- .../kanbanBoard/columnContainer.jsx | 8 +++---- .../components/kanbanBoard/kanbanBoard.jsx | 5 +++-- .../src/components/kanbanBoard/taskCard.jsx | 16 +++++++------- .../components/navigations/IconSideNav.jsx | 11 +++------- 6 files changed, 18 insertions(+), 60 deletions(-) delete mode 100644 frontend/src/components/icons/plusIcon.jsx delete mode 100644 frontend/src/components/icons/trashIcon.jsx diff --git a/frontend/src/components/icons/plusIcon.jsx b/frontend/src/components/icons/plusIcon.jsx deleted file mode 100644 index 7363a7b..0000000 --- a/frontend/src/components/icons/plusIcon.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -function PlusIcon() { - return ( - - - - ); -} - -export default PlusIcon; diff --git a/frontend/src/components/icons/trashIcon.jsx b/frontend/src/components/icons/trashIcon.jsx deleted file mode 100644 index c7721fd..0000000 --- a/frontend/src/components/icons/trashIcon.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; - -function TrashIcon() { - return React.createElement( - "svg", - { - xmlns: "http://www.w3.org/2000/svg", - fill: "none", - viewBox: "0 0 24 24", - strokeWidth: 1.5, - className: "w-6 h-6", - }, - React.createElement("path", { - strokeLinecap: "round", - strokeLinejoin: "round", - d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0", - }) - ); -} - -export default TrashIcon; diff --git a/frontend/src/components/kanbanBoard/columnContainer.jsx b/frontend/src/components/kanbanBoard/columnContainer.jsx index 102e424..aa09993 100644 --- a/frontend/src/components/kanbanBoard/columnContainer.jsx +++ b/frontend/src/components/kanbanBoard/columnContainer.jsx @@ -1,8 +1,8 @@ import { SortableContext, useSortable } from "@dnd-kit/sortable"; -import TrashIcon from "../icons/trashIcon"; +import { BsFillTrashFill } from "react-icons/bs" +import { AiOutlinePlusCircle } from "react-icons/ai" import { CSS } from "@dnd-kit/utilities"; import { useMemo, useState } from "react"; -import PlusIcon from "../icons/plusIcon"; import TaskCard from "./taskCard"; function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks, deleteTask, updateTask }) { @@ -124,7 +124,7 @@ function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks px-1 py-2 "> - +
    @@ -142,7 +142,7 @@ function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks onClick={() => { createTask(column.id); }}> - + Add task
    diff --git a/frontend/src/components/kanbanBoard/kanbanBoard.jsx b/frontend/src/components/kanbanBoard/kanbanBoard.jsx index 743d8a9..e9b5257 100644 --- a/frontend/src/components/kanbanBoard/kanbanBoard.jsx +++ b/frontend/src/components/kanbanBoard/kanbanBoard.jsx @@ -1,10 +1,11 @@ -import PlusIcon from "../icons/plusIcon"; import { useMemo, useState } from "react"; import ColumnContainer from "./columnContainer"; import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; import { SortableContext, arrayMove } from "@dnd-kit/sortable"; import { createPortal } from "react-dom"; import TaskCard from "./taskCard"; +import { AiOutlinePlusCircle } from "react-icons/ai" + const defaultCols = [ { @@ -155,7 +156,7 @@ function KanbanBoard() { flex gap-2 "> - + Add Column
    diff --git a/frontend/src/components/kanbanBoard/taskCard.jsx b/frontend/src/components/kanbanBoard/taskCard.jsx index 9dcafa7..026b335 100644 --- a/frontend/src/components/kanbanBoard/taskCard.jsx +++ b/frontend/src/components/kanbanBoard/taskCard.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import TrashIcon from "../icons/trashIcon"; +import { BsFillTrashFill } from "react-icons/bs" import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; @@ -33,7 +33,7 @@ function TaskCard({ task, deleteTask, updateTask }) { style={style} className=" opacity-30 - bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl border-2 border-rose-500 cursor-grab relative + bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl border-2 border-gray-400 cursor-grab relative " /> ); @@ -46,11 +46,11 @@ function TaskCard({ task, deleteTask, updateTask }) { style={style} {...attributes} {...listeners} - className="bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl hover:ring-2 hover:ring-inset hover:ring-rose-500 cursor-grab relative"> + className="bg-mainBackgroundColor p-2.5 h-[100px] min-h-[100px] items-center flex text-left rounded-xl hover:ring-2 hover:ring-inset bg-zinc-400 ring-zinc-950 cursor-grab relative">