32
src/App.tsx
32
src/App.tsx
@@ -7,19 +7,39 @@ import { PageRouting } from './const/PageRouting';
|
|||||||
import LoginPage from './ui/page/account/login/LoginPage';
|
import LoginPage from './ui/page/account/login/LoginPage';
|
||||||
import ResetPasswordPage from './ui/page/account/resetPassword/ResetPasswordPage';
|
import ResetPasswordPage from './ui/page/account/resetPassword/ResetPasswordPage';
|
||||||
import { HomePage } from './ui/page/home/HomePage';
|
import { HomePage } from './ui/page/home/HomePage';
|
||||||
|
import type { AuthData } from './data/AuthData';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { authData } = useAuthStore();
|
const { authData, login } = useAuthStore();
|
||||||
|
useEffect(() => {
|
||||||
|
const autoLogin = localStorage.getItem('autoLogin') === 'true';
|
||||||
|
if (autoLogin) {
|
||||||
|
const stored = localStorage.getItem('auth-storage');
|
||||||
|
if (stored) {
|
||||||
|
const storedAuthData = JSON.parse(stored).state as AuthData;
|
||||||
|
login(storedAuthData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout />}>
|
<Route element={<Layout />}>
|
||||||
<Route element={<LoginPage />} path={PageRouting["LOGIN"].path} />
|
{
|
||||||
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
|
!authData
|
||||||
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
|
? <>
|
||||||
{!authData ? <Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" /> : null}
|
<Route element={<LoginPage />} path={PageRouting["LOGIN"].path} />
|
||||||
<Route element={<HomePage />} path={PageRouting["HOME"].path} />
|
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
|
||||||
|
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
|
||||||
|
<Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" />
|
||||||
|
</>
|
||||||
|
: <>
|
||||||
|
<Route element={<Navigate to={PageRouting["HOME"].path} />} path="*" />
|
||||||
|
<Route element={<HomePage />} path={PageRouting["HOME"].path} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import type {
|
|||||||
InternalAxiosRequestConfig,
|
InternalAxiosRequestConfig,
|
||||||
} from "axios";
|
} from "axios";
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
import type { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse';
|
import { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse';
|
||||||
|
import type { AuthData } from '@/data/AuthData';
|
||||||
|
|
||||||
export class BaseNetwork {
|
export class BaseNetwork {
|
||||||
protected instance: AxiosInstance;
|
protected instance: AxiosInstance;
|
||||||
@@ -98,30 +99,16 @@ export class BaseNetwork {
|
|||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.get<RefreshAccessTokenResponse>(
|
await this.refreshToken();
|
||||||
'/account/refresh-access-token',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${refreshToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const newAccessToken = response.data.accessToken;
|
const newAccessToken = useAuthStore.getState().authData!.accessToken;
|
||||||
const newRefreshToken = response.data.refreshToken;
|
|
||||||
|
|
||||||
useAuthStore.getState().login({
|
|
||||||
...authData,
|
|
||||||
accessToken: newAccessToken,
|
|
||||||
refreshToken: newRefreshToken
|
|
||||||
});
|
|
||||||
|
|
||||||
this.refreshQueue.forEach((cb) => cb(newAccessToken));
|
this.refreshQueue.forEach((cb) => cb(newAccessToken));
|
||||||
this.refreshQueue = [];
|
this.refreshQueue = [];
|
||||||
originalRequest.headers = {
|
originalRequest.headers = {
|
||||||
...originalRequest.headers,
|
...originalRequest.headers,
|
||||||
Authorization: `Bearer ${newAccessToken}`,
|
Authorization: `Bearer ${newAccessToken}`,
|
||||||
};
|
} as any;
|
||||||
|
|
||||||
return this.instance(originalRequest);
|
return this.instance(originalRequest);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -143,4 +130,39 @@ export class BaseNetwork {
|
|||||||
protected async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { authPass?: boolean }) {
|
protected async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { authPass?: boolean }) {
|
||||||
return await this.instance.post<T>(url, data, config);
|
return await this.instance.post<T>(url, data, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async refreshToken() {
|
||||||
|
const storedAuth = localStorage.getItem('auth-storage');
|
||||||
|
|
||||||
|
if (!storedAuth) {
|
||||||
|
localStorage.setItem('autoLogin', 'false');
|
||||||
|
throw new Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authData: AuthData = JSON.parse(storedAuth).state;
|
||||||
|
|
||||||
|
if (!authData || !authData.refreshToken) {
|
||||||
|
localStorage.setItem('autoLogin', 'false');
|
||||||
|
throw new Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.get<RefreshAccessTokenResponse>(
|
||||||
|
'/account/refresh-access-token',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${authData.refreshToken}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result.data.success) throw new Error;
|
||||||
|
|
||||||
|
const newAccessToken = result.data.accessToken;
|
||||||
|
const newRefreshToken = result.data.refreshToken;
|
||||||
|
|
||||||
|
useAuthStore.getState().login({
|
||||||
|
accessToken: newAccessToken,
|
||||||
|
refreshToken: newRefreshToken
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AuthData } from '@/data/AuthData';
|
import type { AuthData } from '@/data/AuthData';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
|
||||||
interface AuthStoreProps {
|
interface AuthStoreProps {
|
||||||
authData: AuthData | undefined;
|
authData: AuthData | undefined;
|
||||||
@@ -7,17 +8,23 @@ interface AuthStoreProps {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthStoreProps>((set) => ({
|
const storage = sessionStorage;
|
||||||
authData: undefined,
|
export const useAuthStore = create<AuthStoreProps>()(
|
||||||
login: (data: AuthData) => {
|
persist(
|
||||||
set({ authData: data });
|
(set) => ({
|
||||||
Object.entries(data)
|
authData: undefined,
|
||||||
.forEach((entry) => {
|
login: (data: AuthData) => {
|
||||||
localStorage.setItem(entry[0], entry[1]);
|
set({ authData: data });
|
||||||
})
|
},
|
||||||
},
|
logout: () => {
|
||||||
logout: () => {
|
localStorage.setItem('autoLogin', 'false');
|
||||||
set({ authData: undefined });
|
localStorage.removeItem('auth-storage');
|
||||||
localStorage.clear();
|
set({ authData: undefined });
|
||||||
}
|
}
|
||||||
}));
|
}),
|
||||||
|
{
|
||||||
|
name: 'auth-storage',
|
||||||
|
storage: createJSONStorage(() => storage)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -4,7 +4,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { PageRouting } from '@/const/PageRouting';
|
import { PageRouting } from '@/const/PageRouting';
|
||||||
@@ -15,9 +15,12 @@ import { LoginRequest } from '@/data/request/account/LoginRequest';
|
|||||||
import { AccountNetwork } from '@/network/AccountNetwork';
|
import { AccountNetwork } from '@/network/AccountNetwork';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useAuthStore } from '@/store/authStore';
|
import { useAuthStore } from '@/store/authStore';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||||
const { login } = useAuthStore();
|
const { login } = useAuthStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const accountNetwork = new AccountNetwork();
|
const accountNetwork = new AccountNetwork();
|
||||||
@@ -30,6 +33,10 @@ export default function LoginPage() {
|
|||||||
});
|
});
|
||||||
const { id, password } = { id: loginForm.watch('id'), password: loginForm.watch('password') };
|
const { id, password } = { id: loginForm.watch('id'), password: loginForm.watch('password') };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem('autoLogin', `${autoLogin}`)
|
||||||
|
}, [autoLogin]);
|
||||||
|
|
||||||
const moveToSignUpPage = useCallback(() => {
|
const moveToSignUpPage = useCallback(() => {
|
||||||
navigate(PageRouting["SIGN_UP"].path);
|
navigate(PageRouting["SIGN_UP"].path);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -149,7 +156,20 @@ export default function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
</Controller>
|
</Controller>
|
||||||
|
<div className="flex flex-row gap-2 mt-2">
|
||||||
|
<Checkbox
|
||||||
|
className={[
|
||||||
|
"data-[state=checked]:bg-indigo-500 data-[state=checked]:text-white"
|
||||||
|
, "data-[state=checked]:outline-none data-[state=checked]:border-0"
|
||||||
|
].join(' ')}
|
||||||
|
id="auto-login"
|
||||||
|
checked={autoLogin}
|
||||||
|
onCheckedChange={(value) => setAutoLogin(value === true)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="auto-login">자동 로그인</Label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter
|
<CardFooter
|
||||||
className="w-full flex flex-col items-center gap-5"
|
className="w-full flex flex-col items-center gap-5"
|
||||||
|
|||||||
Reference in New Issue
Block a user