Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a893cf5

Browse files
committed
updated
1 parent 0a0f1fe commit a893cf5

File tree

13 files changed

+182
-84
lines changed

13 files changed

+182
-84
lines changed

packages/client/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
"react-scripts": "5.0.1",
2323
"react-toastify": "^9.0.5",
2424
"server": "1.0.0",
25+
"tailwind-merge": "^1.3.0",
2526
"typescript": "^4.7.4",
2627
"web-vitals": "^2.1.4",
27-
"zod": "^3.17.3"
28+
"zod": "^3.17.3",
29+
"zustand": "^4.0.0-rc.1"
2830
},
2931
"scripts": {
3032
"start": "react-scripts start",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, { useCallback } from 'react';
2+
import { Controller, useController, useFormContext } from 'react-hook-form';
3+
import useStore from '../store';
4+
import Spinner from './Spinner';
5+
6+
type FileUpLoaderProps = {
7+
name: string;
8+
};
9+
const FileUpLoader: React.FC<FileUpLoaderProps> = ({ name }) => {
10+
const {
11+
control,
12+
formState: { errors },
13+
} = useFormContext();
14+
const { field } = useController({ name, control });
15+
const store = useStore();
16+
17+
const onFileDrop = useCallback(
18+
async (e: React.SyntheticEvent<EventTarget>) => {
19+
const target = e.target as HTMLInputElement;
20+
if (!target.files) return;
21+
const newFile = Object.values(target.files).map((file: File) => file);
22+
const formData = new FormData();
23+
formData.append('file', newFile[0]);
24+
formData.append('upload_preset', 'trpc-api');
25+
26+
store.setUploadingImage(true);
27+
const data = await fetch(
28+
'https://api.cloudinary.com/v1_1/Codevo/image/upload',
29+
{
30+
method: 'POST',
31+
body: formData,
32+
}
33+
)
34+
.then((res) => {
35+
store.setUploadingImage(false);
36+
37+
return res.json();
38+
})
39+
.catch((err) => {
40+
store.setUploadingImage(false);
41+
console.log(err);
42+
});
43+
44+
if (data.secure_url) {
45+
field.onChange(data.secure_url);
46+
}
47+
},
48+
49+
[field, store]
50+
);
51+
52+
return (
53+
<Controller
54+
name={name}
55+
defaultValue=''
56+
control={control}
57+
render={({ field: { name, onBlur, ref } }) => (
58+
<>
59+
<div className='mb-2 flex justify-between items-center'>
60+
<div>
61+
<span className='block mb-2'>Choose profile photo</span>
62+
<input
63+
className='block text-sm mb-2 text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 file:text-violet-700 hover:file:bg-violet-100'
64+
type='file'
65+
name={name}
66+
onBlur={onBlur}
67+
ref={ref}
68+
onChange={onFileDrop}
69+
multiple={false}
70+
accept='image/jpg, image/png, image/jpeg'
71+
/>
72+
</div>
73+
<div>
74+
{store.uploadingImage && <Spinner color='text-yellow-400' />}
75+
</div>
76+
</div>
77+
<p
78+
className={`text-red-500 text-xs italic mb-2 ${
79+
errors[name] ? 'visible' : 'invisible'
80+
}`}
81+
>
82+
{errors[name] && (errors[name]?.message as string)}
83+
</p>
84+
</>
85+
)}
86+
/>
87+
);
88+
};
89+
90+
export default FileUpLoader;

packages/client/src/components/Header.tsx

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useQueryClient } from 'react-query';
22
import { Link } from 'react-router-dom';
3-
import { useStateContext } from '../context';
3+
import useStore from '../store';
44
import { trpc } from '../trpc';
55

66
const Header = () => {
7-
const stateContext = useStateContext();
8-
const user = stateContext.state.authUser;
7+
const store = useStore();
8+
const user = store.authUser;
99

1010
const queryClient = useQueryClient();
1111
const { mutate: logoutUser } = trpc.useMutation(['auth.logout'], {
@@ -14,7 +14,6 @@ const Header = () => {
1414
document.location.href = '/login';
1515
},
1616
onError(error) {
17-
console.log(error);
1817
queryClient.clear();
1918
document.location.href = '/login';
2019
},

packages/client/src/components/Spinner.tsx

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import React from 'react';
2+
import { twMerge } from 'tailwind-merge';
23
type SpinnerProps = {
34
width?: number;
45
height?: number;
6+
color?: string;
7+
bgColor?: string;
58
};
6-
const Spinner: React.FC<SpinnerProps> = ({ width = 5, height = 5 }) => {
9+
const Spinner: React.FC<SpinnerProps> = ({
10+
width = 5,
11+
height = 5,
12+
color,
13+
bgColor,
14+
}) => {
715
return (
816
<svg
917
role='status'
10-
className={`w-5 h-5 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
18+
className={twMerge(
19+
'w-5 h-5 mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600',
20+
`w-${width} h-${height} ${color} ${bgColor}`
21+
)}
1122
viewBox='0 0 100 101'
1223
fill='none'
1324
xmlns='http://www.w3.org/2000/svg'

packages/client/src/components/requireUser.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { useCookies } from 'react-cookie';
22
import { Navigate, Outlet, useLocation } from 'react-router-dom';
3-
import { useStateContext } from '../context';
4-
import { IUser } from '../context/types';
3+
import { IUser } from '../libs/types';
4+
import useStore from '../store';
55
import { trpc } from '../trpc';
66
import FullScreenLoader from './FullScreenLoader';
77

88
const RequireUser = ({ allowedRoles }: { allowedRoles: string[] }) => {
99
const [cookies] = useCookies(['logged_in']);
1010
const location = useLocation();
11-
const stateContext = useStateContext();
11+
const store = useStore();
1212

1313
const {
1414
isLoading,
@@ -18,7 +18,7 @@ const RequireUser = ({ allowedRoles }: { allowedRoles: string[] }) => {
1818
retry: 1,
1919
select: (data) => data.data.user,
2020
onSuccess: (data) => {
21-
stateContext.dispatch({ type: 'SET_USER', payload: data as IUser });
21+
store.setAuthUser(data as IUser);
2222
},
2323
onError: (error) => {
2424
console.log(error);

packages/client/src/context/index.tsx

-57
This file was deleted.

packages/client/src/index.tsx

+5-7
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client';
33
import { BrowserRouter as Router } from 'react-router-dom';
44
import { ToastContainer } from 'react-toastify';
55
import App from './App';
6-
import { StateContextProvider } from './context';
6+
77
import './global.css';
88
import 'react-toastify/dist/ReactToastify.css';
99

@@ -12,11 +12,9 @@ const root = ReactDOM.createRoot(
1212
);
1313
root.render(
1414
<React.StrictMode>
15-
<StateContextProvider>
16-
<Router>
17-
<App />
18-
<ToastContainer />
19-
</Router>
20-
</StateContextProvider>
15+
<Router>
16+
<App />
17+
<ToastContainer />
18+
</Router>
2119
</React.StrictMode>
2220
);

packages/client/src/context/types.ts renamed to packages/client/src/libs/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export interface IUser {
55
photo: string;
66
_id: string;
77
id: string;
8-
created_at: string;
9-
updated_at: string;
8+
createdAt: string;
9+
updatedAt: string;
1010
__v: number;
1111
}

packages/client/src/middleware/AuthMiddleware.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { useCookies } from 'react-cookie';
2-
import { useStateContext } from '../context';
32
import FullScreenLoader from '../components/FullScreenLoader';
43
import React from 'react';
54
import { trpc } from '../trpc';
6-
import { IUser } from '../context/types';
5+
import { IUser } from '../libs/types';
76
import { useQueryClient } from 'react-query';
7+
import useStore from '../store';
88

99
type AuthMiddlewareProps = {
1010
children: React.ReactElement;
1111
};
1212

1313
const AuthMiddleware: React.FC<AuthMiddlewareProps> = ({ children }) => {
1414
const [cookies] = useCookies(['logged_in']);
15-
const stateContext = useStateContext();
15+
const store = useStore();
1616

1717
const queryClient = useQueryClient();
1818
const { refetch } = trpc.useQuery(['auth.refresh'], {
@@ -28,7 +28,7 @@ const AuthMiddleware: React.FC<AuthMiddlewareProps> = ({ children }) => {
2828
retry: 1,
2929
select: (data) => data.data.user,
3030
onSuccess: (data) => {
31-
stateContext.dispatch({ type: 'SET_USER', payload: data as IUser });
31+
store.setAuthUser(data as IUser);
3232
},
3333
onError: (error) => {
3434
let retryRequest = true;

packages/client/src/pages/profile.page.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { useStateContext } from '../context';
1+
import useStore from '../store';
22

33
const ProfilePage = () => {
4-
const stateContext = useStateContext();
4+
const store = useStore();
55

6-
const user = stateContext.state.authUser;
6+
const user = store.authUser;
77

88
return (
99
<section className='bg-ct-blue-600 min-h-screen pt-20'>

packages/client/src/pages/register.page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import FormInput from '../components/FormInput';
77
import { LoadingButton } from '../components/LoadingButton';
88
import { toast } from 'react-toastify';
99
import { trpc } from '../trpc';
10+
import FileUpLoader from '../components/FileUpload';
1011

1112
const registerSchema = object({
1213
name: string().min(1, 'Full name is required').max(100),
1314
email: string()
1415
.min(1, 'Email address is required')
1516
.email('Email Address is invalid'),
17+
photo: string().min(1, 'Photo is required').url('Photo URL is invalid'),
1618
password: string()
1719
.min(1, 'Password is required')
1820
.min(8, 'Password must be more than 8 characters')
@@ -88,6 +90,7 @@ const RegisterPage = () => {
8890
name='passwordConfirm'
8991
type='password'
9092
/>
93+
<FileUpLoader name='photo' />
9194
<span className='block'>
9295
Already have an account?{' '}
9396
<Link to='/login' className='text-ct-blue-600'>

packages/client/src/store/index.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import create from 'zustand';
2+
import { IUser } from '../libs/types';
3+
4+
type Store = {
5+
authUser: IUser | null;
6+
uploadingImage: boolean;
7+
pageLoading: boolean;
8+
openModal: boolean;
9+
setAuthUser: (user: IUser) => void;
10+
setUploadingImage: (isUploading: boolean) => void;
11+
setPageLoading: (isLoading: boolean) => void;
12+
setOpenModal: (isOpen: boolean) => void;
13+
};
14+
15+
const useStore = create<Store>((set) => ({
16+
authUser: null,
17+
uploadingImage: false,
18+
pageLoading: false,
19+
openModal: false,
20+
setAuthUser: (user) => set((state) => ({ ...state, authUser: user })),
21+
setUploadingImage: (isUploading) =>
22+
set((state) => ({ ...state, uploadingImage: isUploading })),
23+
setPageLoading: (isLoading) =>
24+
set((state) => ({ ...state, pageLoading: isLoading })),
25+
setOpenModal: (isOpen) => set((state) => ({ ...state, openModal: isOpen })),
26+
}));
27+
28+
export default useStore;

0 commit comments

Comments
 (0)