Skip to content

Commit

Permalink
Complete dashboard data display,GET request from server and SkillForm UI
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenfamj committed Apr 21, 2022
1 parent 50febbe commit cf5446d
Show file tree
Hide file tree
Showing 36 changed files with 767 additions and 46 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"cSpell.ignoreWords": ["persistor", "semibold"]
"cSpell.ignoreWords": ["metas", "persistor", "semibold"]
}
18 changes: 18 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 0 additions & 1 deletion client/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&display=swap"
rel="stylesheet"
/>
<title>React Redux App</title>
</head>
<body>
<div id="root"></div>
Expand Down
3 changes: 2 additions & 1 deletion client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -22,7 +23,7 @@ function App() {
element={<ProtectedRoute path='/login' element={<Dashboard />} />}
/>
<Route path='/about' element={<ProtectedRoute path='/login' element={<About />} />} />
<Route path='/profile' element={<ProtectedRoute path='/login' element={<About />} />} />
<Route path='/profile' element={<ProtectedRoute path='/login' element={<Profile />} />} />
</Routes>
</Router>
);
Expand Down
24 changes: 12 additions & 12 deletions client/src/components/Authentication/Authentication.tsx
Original file line number Diff line number Diff line change
@@ -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 <></>;
};
57 changes: 57 additions & 0 deletions client/src/components/CardOptions/CardOptions.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Popover className='relative'>
{({ open }) => (
<>
<Popover.Button className='flex items-center justify-center rounded-full w-7 h-7 hover:bg-slate-400'>
<DotsHorizontalIcon
className={`${open ? '' : ''}
h-6 w-6 text-white group-hover:text-opacity-80 transition ease-in-out duration-150`}
aria-hidden='true'
/>
</Popover.Button>
<Transition
as={Fragment}
enter='transition ease-out duration-200'
enterFrom='opacity-0 translate-y-1'
enterTo='opacity-100 translate-y-0'
leave='transition ease-in duration-150'
leaveFrom='opacity-100 translate-y-0'
leaveTo='opacity-0 translate-y-1'
>
<Popover.Panel className='absolute right-0 z-10 mt-1 '>
<div className='overflow-hidden rounded-lg shadow-lg '>
<div className='flex flex-col p-2 space-y-2 bg-white '>
{/* Edit button */}
<button
type='button'
onClick={() => {}}
className='flex items-center px-3 py-1 rounded-md w-28 hover:cursor-pointer hover:bg-slate-300'
>
<PencilIcon className='w-4 h-4 ' />
<p className='ml-4 text-sm'>Edit</p>
</button>
{/* Delete button */}
<button
type='button'
onClick={() => {}}
className='flex items-center px-3 py-1 rounded-md w-28 hover:cursor-pointer hover:bg-slate-300'
>
<TrashIcon className='w-4 h-4 ' />
<p className='ml-4 text-sm'>Delete</p>
</button>
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
);
};

export default CardOptions;
2 changes: 1 addition & 1 deletion client/src/components/GlassDialog/GlassDialog.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
2 changes: 0 additions & 2 deletions client/src/components/GlassForm/GlassForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import React from 'react';

// Import Form Components
import GlassInput from './GlassInput/GlassInput';

Expand Down
6 changes: 3 additions & 3 deletions client/src/components/GlassForm/GlassInput/GlassInput.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
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 (
<div className='flex flex-col mb-3 w-72'>
<div className='relative flex flex-col mb-3 w-72'>
<input
title='input'
{...inputProps}
value={values[inputProps.name]}
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'
/>
<span className='absolute z-10 invisible p-2 pr-1 rounded-lg w-[400px] peer-invalid:visible bg-white/70 left-[83%] after:content-[" "] after:absolute after:top-1/2 after:border-solid after:border-transparent after:border-r-white/70 after:right-full after:border-[9px] after:-translate-y-2'>
<span className='absolute z-10 invisible p-2 pr-1 rounded-lg w-[400px] peer-invalid:visible bg-white/70 left-full translate-x-5 after:content-[" "] after:absolute after:top-1/2 after:border-solid after:border-transparent after:border-r-white/70 after:right-full after:border-[9px] after:-translate-y-2'>
<p className='text-xs text-indigo-900'>{errorMessage}</p>
</span>
</div>
Expand Down
13 changes: 7 additions & 6 deletions client/src/components/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ const Navbar = () => {
classes.filter(Boolean).join(' ');

return (
<Disclosure as='nav' className=''>
<Disclosure as='nav' className='mb-14'>
{({ open }) => (
<>
<div className='w-screen px-2 mx-auto sm:px-6 lg:px-8'>
<div className='relative inset-y-0 flex items-center justify-between h-16 '>
{/* Dropdown button for small screen */}
<div className='absolute left-0 flex items-center sm:hidden'>
<Disclosure.Button className='inline-flex items-center justify-center p-2 text-black rounded-md focus:outline-none '>
<Disclosure.Button className='inline-flex items-center justify-center text-black rounded-md focus:outline-none '>
{open ? (
<XIcon className='block w-7 h-7' aria-hidden='true' />
) : (
Expand All @@ -70,7 +70,7 @@ const Navbar = () => {
<div className='flex items-center justify-center flex-1 sm:justify-start'>
<Link to='/dashboard' className='flex items-center flex-shrink-0'>
<BrandLogo className='block w-auto h-10 fill-indigo-900 lg:hidden' />
<h1 className='hidden text-3xl font-black text-indigo-900 lg:block'>STICKI</h1>
<h1 className='hidden text-3xl font-black text-indigo-900 lg:block'>SKIL</h1>
</Link>
<div className='hidden sm:block sm:ml-8'>
<div className='flex space-x-4'>
Expand All @@ -95,11 +95,12 @@ const Navbar = () => {
{/* Notification button and profile */}
<div className='absolute inset-y-0 right-0 flex items-center pr-2 space-x-4 sm:inset-auto sm:static sm:pr-0 sm:ml-6'>
<button
title='notification'
title='Notification'
type='button'
className='p-1 text-black rounded-full hover:bg-black/60 hover:text-white'
className='flex items-center justify-center p-1 text-black rounded-full hover:bg-black/60 hover:text-white'
>
<StatusOnlineIcon className='w-6 h-6' />
<span className='absolute inline-flex rounded-full w-7 h-7 bg-slate-500 opacity-20 animate-ping '></span>
<StatusOnlineIcon className='relative inline-flex w-6 h-6 ' />
</button>
<button
title='setting'
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ProtectedRoute/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ProtectedRoute = ({ path, element: Element }: ProtectedRouteProps) => {

if (isAuthenticated) {
return (
<div className='w-screen h-screen bg-radial-gradient-background'>
<div className='w-screen min-h-screen bg-radial-gradient-background'>
<Navbar />
{Element}
</div>
Expand Down
99 changes: 99 additions & 0 deletions client/src/components/SkillAdd/SkillAdd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { Fragment, useRef, useState } from 'react';

// Import from HeadlessUI
import { Transition, Dialog } from '@headlessui/react';

// Import components
import Button from './SkillAddButton/SkillAddButton';
import SkillForm from '../SkillForm/SkillForm';

// Import interfaces
import { skillInput } from '../../interfaces/formInputs';
import { statusEnum } from '../../interfaces/skillApiResponse';

interface PropTypes {
children?: React.ReactNode;
}

const SkillAdd = ({ children }: PropTypes) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const initialFocusRef = useRef(null);

const openModal = (): void => {
setIsOpen(true);
};

const closeModal = (): void => {
setIsOpen(false);
};

// Form States
const initialFormStates: skillInput = {
title: '',
description: '',
status: statusEnum.Planned,
url: '',
};

return (
<>
{!isOpen && (
<div>
<Transition
show={!isOpen}
enter='transition-opacity duration-150'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='transition-opacity duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<Button openModal={openModal} />
</Transition>
</div>
)}
<Transition as={Fragment} show={isOpen}>
<Dialog
as='div'
className='fixed inset-0 z-10'
onClose={closeModal}
initialFocus={initialFocusRef}
>
<div className='min-h-screen'>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-200'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<Dialog.Overlay className='fixed inset-0 bg-slate-700/10 backdrop-blur-sm ' />
</Transition.Child>

<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='translate-y-3 opacity-90'
enterTo='translate-y-0 opacity-100'
leave='ease-in duration-200'
leaveFrom='translate-y-0 '
leaveTo='translate-y-3 '
>
<div className='flex items-center justify-center w-screen h-screen '>
<SkillForm
initialStates={initialFormStates}
formTitle='Create Skill'
closeModal={closeModal}
/>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
);
};

export default SkillAdd;
21 changes: 21 additions & 0 deletions client/src/components/SkillAdd/SkillAddButton/SkillAddButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PlusIcon } from '@heroicons/react/solid';

type PropTypes = {
className?: string;
openModal: () => void;
};

const SkillAddButton = ({ className, openModal }: PropTypes) => {
return (
<button
title='Add skill'
type='button'
className={`flex items-center justify-center w-12 h-12 bg-white rounded-full sm:w-9 sm:h-9 bg-black/30 hover:ring-4 hover:ring-white/80 fixed z-50 transform translate-x-1/2 bottom-2 right-1/2 sm:bottom-0 sm:translate-x-0 sm:relative sm:right-2 sm:ml-6`}
onClick={openModal}
>
<PlusIcon className='w-10 h-10 text-white sm:w-8 sm:h-8' />
</button>
);
};

export default SkillAddButton;
Loading

0 comments on commit cf5446d

Please sign in to comment.