From cf5446d8006053002dafa9c77263a671fafeabe4 Mon Sep 17 00:00:00 2001 From: nguyenfamj Date: Fri, 22 Apr 2022 00:49:56 +0300 Subject: [PATCH] Complete dashboard data display,GET request from server and SkillForm UI --- .vscode/settings.json | 2 +- client/package-lock.json | 18 +++ client/package.json | 1 + client/public/index.html | 1 - client/src/App.tsx | 3 +- .../Authentication/Authentication.tsx | 24 ++-- .../components/CardOptions/CardOptions.tsx | 57 +++++++++ .../components/GlassDialog/GlassDialog.tsx | 2 +- client/src/components/GlassForm/GlassForm.tsx | 2 - .../GlassForm/GlassInput/GlassInput.tsx | 6 +- client/src/components/Navbar/Navbar.tsx | 13 +- .../ProtectedRoute/ProtectedRoute.tsx | 2 +- client/src/components/SkillAdd/SkillAdd.tsx | 99 ++++++++++++++ .../SkillAddButton/SkillAddButton.tsx | 21 +++ .../SkillAdd/StatusList/StatusList.tsx | 72 +++++++++++ client/src/components/SkillCard/SkillCard.tsx | 70 ++++++++++ .../components/SkillForm/SkillForm.styled.ts | 18 +++ client/src/components/SkillForm/SkillForm.tsx | 121 ++++++++++++++++++ client/src/components/SkillTab/SkillTab.tsx | 21 +++ client/src/index.css | 25 ++++ client/src/index.styled.ts | 2 +- .../{apiResponse.ts => authApiResponse.ts} | 0 client/src/interfaces/formInputs.ts | 18 ++- client/src/interfaces/navigation.ts | 1 + client/src/interfaces/skillApiResponse.ts | 21 +++ client/src/pages/Auth/LoginForm/LoginForm.tsx | 10 +- .../pages/Auth/RegisterForm/RegisterForm.tsx | 4 +- client/src/pages/Dashboard/Dashboard.tsx | 109 +++++++++++++++- client/src/pages/Profile/Profile.tsx | 7 + client/src/redux/combinedReducers.ts | 4 +- client/src/redux/services/authApi.ts | 4 +- client/src/redux/services/skillApi.ts | 37 ++++++ client/src/redux/slices/authSlice.ts | 2 +- client/src/redux/slices/skillSlice.ts | 9 ++ client/src/redux/store.ts | 5 +- server/src/api/controllers/skill.js | 2 +- 36 files changed, 767 insertions(+), 46 deletions(-) create mode 100644 client/src/components/CardOptions/CardOptions.tsx create mode 100644 client/src/components/SkillAdd/SkillAdd.tsx create mode 100644 client/src/components/SkillAdd/SkillAddButton/SkillAddButton.tsx create mode 100644 client/src/components/SkillAdd/StatusList/StatusList.tsx create mode 100644 client/src/components/SkillCard/SkillCard.tsx create mode 100644 client/src/components/SkillForm/SkillForm.styled.ts create mode 100644 client/src/components/SkillForm/SkillForm.tsx create mode 100644 client/src/components/SkillTab/SkillTab.tsx rename client/src/interfaces/{apiResponse.ts => authApiResponse.ts} (100%) create mode 100644 client/src/interfaces/skillApiResponse.ts create mode 100644 client/src/pages/Profile/Profile.tsx create mode 100644 client/src/redux/services/skillApi.ts create mode 100644 client/src/redux/slices/skillSlice.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a7b1141..4cb0804 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.ignoreWords": ["persistor", "semibold"] + "cSpell.ignoreWords": ["metas", "persistor", "semibold"] } diff --git a/client/package-lock.json b/client/package-lock.json index a2ba9cf..93bc775 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "@types/react-redux": "^7.1.23", "@types/styled-components": "^5.1.24", "@types/webpack-env": "^1.16.3", + "date-fns": "^2.28.0", "prettier": "^2.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -5798,6 +5799,18 @@ "node": ">=10" } }, + "node_modules/date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==", + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -20201,6 +20214,11 @@ "whatwg-url": "^8.0.0" } }, + "date-fns": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", + "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/client/package.json b/client/package.json index c42194c..60bc956 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "@types/react-redux": "^7.1.23", "@types/styled-components": "^5.1.24", "@types/webpack-env": "^1.16.3", + "date-fns": "^2.28.0", "prettier": "^2.6.0", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/client/public/index.html b/client/public/index.html index 9162ca2..969d821 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -11,7 +11,6 @@ href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&display=swap" rel="stylesheet" /> - React Redux App
diff --git a/client/src/App.tsx b/client/src/App.tsx index f152a16..a70f22f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -8,6 +8,7 @@ import { Authentication } from './components/Authentication/Authentication'; import Auth from './pages/Auth/Auth'; import Dashboard from './pages/Dashboard/Dashboard'; import About from './pages/About/About'; +import Profile from './pages/Profile/Profile'; function App() { return ( @@ -22,7 +23,7 @@ function App() { element={} />} /> } />} /> - } />} /> + } />} /> ); diff --git a/client/src/components/Authentication/Authentication.tsx b/client/src/components/Authentication/Authentication.tsx index dbc6aee..e149883 100644 --- a/client/src/components/Authentication/Authentication.tsx +++ b/client/src/components/Authentication/Authentication.tsx @@ -1,33 +1,33 @@ import { useEffect } from 'react'; // Import from RTK -import { useAuthenticateQuery } from '../../redux/services/authApi'; +import { useLazyAuthenticateQuery } from '../../redux/services/authApi'; import { setUser } from '../../redux/slices/authSlice'; import { useAppDispatch, useAppSelector } from '../../hooks/rtkHook'; export const Authentication = () => { - const { data, error, isFetching } = useAuthenticateQuery(); + const [trigger, result] = useLazyAuthenticateQuery(); // Get isAuthenticated state from store - const { isAuthenticated, user } = useAppSelector((state) => state.auth); + const { user, accessToken } = useAppSelector((state) => state.auth); // Handle authenticateUser and change state in the store const dispatch = useAppDispatch(); const authenticateUser = async () => { - if (isFetching) { - console.log('Loading...'); - } - if (data?.success) { + await trigger(); + if (result?.data?.success) { console.log('Authenticated'); - dispatch(setUser({ user: data?.user })); - } else if (error) { - console.log('Authentication Error'); + dispatch(setUser({ user: result?.data?.user })); + } else if (result.error) { + console.log('Authentication Error', result.error); } }; // Only re-render when data.success (boolean) is changed useEffect(() => { - if (isAuthenticated && user === data?.user) authenticateUser(); - }, [data?.success]); + if (user !== result?.data?.user && accessToken) { + authenticateUser(); + } + }, [result?.data?.user, accessToken]); return <>; }; diff --git a/client/src/components/CardOptions/CardOptions.tsx b/client/src/components/CardOptions/CardOptions.tsx new file mode 100644 index 0000000..a4d7a0a --- /dev/null +++ b/client/src/components/CardOptions/CardOptions.tsx @@ -0,0 +1,57 @@ +import { Fragment } from 'react'; +import { Popover, Transition } from '@headlessui/react'; +import { DotsHorizontalIcon, PencilIcon, TrashIcon } from '@heroicons/react/outline'; + +const CardOptions = () => { + return ( + + {({ open }) => ( + <> + + + + +
+
+ {/* Edit button */} + + {/* Delete button */} + +
+
+
+
+ + )} +
+ ); +}; + +export default CardOptions; diff --git a/client/src/components/GlassDialog/GlassDialog.tsx b/client/src/components/GlassDialog/GlassDialog.tsx index 0f5e7dd..5e73dfa 100644 --- a/client/src/components/GlassDialog/GlassDialog.tsx +++ b/client/src/components/GlassDialog/GlassDialog.tsx @@ -1,6 +1,6 @@ import { useState, Fragment } from 'react'; import { Dialog, Transition } from '@headlessui/react'; -import { errorAlert } from '../../interfaces/apiResponse'; +import { errorAlert } from '../../interfaces/authApiResponse'; const GlassDialog = ({ title, message }: errorAlert) => { const [isOpen, setIsOpen] = useState(true); diff --git a/client/src/components/GlassForm/GlassForm.tsx b/client/src/components/GlassForm/GlassForm.tsx index b68e3d5..42fd2e6 100644 --- a/client/src/components/GlassForm/GlassForm.tsx +++ b/client/src/components/GlassForm/GlassForm.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - // Import Form Components import GlassInput from './GlassInput/GlassInput'; diff --git a/client/src/components/GlassForm/GlassInput/GlassInput.tsx b/client/src/components/GlassForm/GlassInput/GlassInput.tsx index 53f31a4..77e0144 100644 --- a/client/src/components/GlassForm/GlassInput/GlassInput.tsx +++ b/client/src/components/GlassForm/GlassInput/GlassInput.tsx @@ -1,13 +1,13 @@ import React from 'react'; // Import interface -import { inputMetas, glassInputProps, inputsValueType } from '../../../interfaces/formInputs'; +import { inputMetas, glassInputProps } from '../../../interfaces/formInputs'; const GlassInput = ({ metas, onChange, values }: glassInputProps) => { const { id, errorMessage, label, required, ...inputProps }: inputMetas = metas; return ( -
+
{ onChange={onChange} className='p-3 pl-5 text-sm border shadow-sm border-slate-200 peer w-72 rounded-xl bg-white/40 placeholder:text-black/50 placeholder:text-sm focus:outline-none focus:border-indigo-900 focus:ring-1 focus:ring-indigo-500 invalid:border-red-500 focus:invalid:border-red-500 focus:invalid:ring-red-400 invalid:text-red-400' /> - +

{errorMessage}

diff --git a/client/src/components/Navbar/Navbar.tsx b/client/src/components/Navbar/Navbar.tsx index c2100e1..262ea52 100644 --- a/client/src/components/Navbar/Navbar.tsx +++ b/client/src/components/Navbar/Navbar.tsx @@ -50,14 +50,14 @@ const Navbar = () => { classes.filter(Boolean).join(' '); return ( - + {({ open }) => ( <>
{/* Dropdown button for small screen */}
- + {open ? (