add: full auth functionality
add: zustand auth store add: jwt auto refresh
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { ReactQueryProvider } from '@/components/providers/ReactQueryProvider'
|
import { ReactQueryProvider } from '@/components/providers/ReactQueryProvider'
|
||||||
|
|
||||||
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
+24
-11
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, type FormEvent, type ChangeEvent, useEffect } from 'react'
|
import React, { useState, type FormEvent, type ChangeEvent, useEffect } from 'react'
|
||||||
import { useAuthStore } from '@/zustand/userStore';
|
import { useAuthStore } from '@/zustand/authStore';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import StepEnterPassword from '@/features/auth/components/StepEnterPassword';
|
import StepEnterPassword from '@/features/auth/components/StepEnterPassword';
|
||||||
import StepEnterNumber from '@/features/auth/components/StepEnterNumber';
|
import StepEnterNumber from '@/features/auth/components/StepEnterNumber';
|
||||||
@@ -12,6 +12,8 @@ import StepNewPassword from '@/features/auth/components/StepNewPassword';
|
|||||||
import { useLogin } from '@/hooks/auth/useLogin';
|
import { useLogin } from '@/hooks/auth/useLogin';
|
||||||
import { useSignup } from '@/hooks/auth/useSignup';
|
import { useSignup } from '@/hooks/auth/useSignup';
|
||||||
import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists';
|
import { useCheckUserExistsLazy } from '@/hooks/auth/useCheckUserExists';
|
||||||
|
import { useOtpRequest } from '@/hooks/auth/useOtpRequest';
|
||||||
|
import { useOtpValidation } from '@/hooks/auth/useOtpValidation';
|
||||||
|
|
||||||
type Props = object
|
type Props = object
|
||||||
|
|
||||||
@@ -29,7 +31,9 @@ function AuthIndex({ }: Props) {
|
|||||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
|
||||||
const loginMutation = useLogin()
|
const loginMutation = useLogin()
|
||||||
const signupMutation = useSignup()
|
const signupMutation = useSignup()
|
||||||
const checkUserExistsMuation = useCheckUserExistsLazy()
|
const { run: runUserExistCheck } = useCheckUserExistsLazy()
|
||||||
|
const { run: runOtpRequest } = useOtpRequest();
|
||||||
|
const { run: runOtpValidation } = useOtpValidation();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -42,6 +46,7 @@ function AuthIndex({ }: Props) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (step === AUTH_STEP.ENTER_OTP) {
|
if (step === AUTH_STEP.ENTER_OTP) {
|
||||||
console.log("REQUEST OTP")
|
console.log("REQUEST OTP")
|
||||||
|
runOtpRequest(number);
|
||||||
}
|
}
|
||||||
}, [step]);
|
}, [step]);
|
||||||
|
|
||||||
@@ -67,7 +72,7 @@ function AuthIndex({ }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const onClick = async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const target = e.target as HTMLButtonElement;
|
const target = e.target as HTMLButtonElement;
|
||||||
|
|
||||||
@@ -82,7 +87,12 @@ function AuthIndex({ }: Props) {
|
|||||||
}
|
}
|
||||||
else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) {
|
else if (target.id === AUTH_PAGE_ELEMENT.RESEND_OTP) {
|
||||||
if (!timerRunning) {
|
if (!timerRunning) {
|
||||||
restart();
|
try {
|
||||||
|
await runOtpRequest(number);
|
||||||
|
restart();
|
||||||
|
} catch {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -91,7 +101,7 @@ function AuthIndex({ }: Props) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (step == AUTH_STEP.ENTER_NUMBER) {
|
if (step == AUTH_STEP.ENTER_NUMBER) {
|
||||||
|
|
||||||
if (await checkUserExistsMuation.run(number)) {
|
if (await runUserExistCheck(number)) {
|
||||||
setStep(AUTH_STEP.ENTER_PASSWORD)
|
setStep(AUTH_STEP.ENTER_PASSWORD)
|
||||||
} else {
|
} else {
|
||||||
setStep(AUTH_STEP.ENTER_OTP);
|
setStep(AUTH_STEP.ENTER_OTP);
|
||||||
@@ -107,13 +117,16 @@ function AuthIndex({ }: Props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (step == AUTH_STEP.ENTER_OTP) {
|
else if (step == AUTH_STEP.ENTER_OTP) {
|
||||||
|
if (!await runOtpValidation({ phone: number, otp })) {
|
||||||
|
setOtp('');
|
||||||
|
console.log('Wrong otp');
|
||||||
|
}
|
||||||
stop();
|
stop();
|
||||||
setStep(AUTH_STEP.ENTER_NEW_PASSWORD)
|
if (await runUserExistCheck(number)) {
|
||||||
// if (await validatePhoneNumber(number)) {
|
setStep(AUTH_STEP.ENTER_RESET_PASSWORD)
|
||||||
// setStep(AUTH_STEP.ENTER_RESET_PASSWORD)
|
} else {
|
||||||
// } else {
|
setStep(AUTH_STEP.ENTER_NEW_PASSWORD)
|
||||||
// setStep(AUTH_STEP.ENTER_NEW_PASSWORD)
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
else if (step == AUTH_STEP.ENTER_RESET_PASSWORD) {
|
else if (step == AUTH_STEP.ENTER_RESET_PASSWORD) {
|
||||||
console.log("Password changed")
|
console.log("Password changed")
|
||||||
|
|||||||
+10
-4
@@ -1,18 +1,24 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useAuthStore } from "@/zustand/userStore";
|
import { useAuthStore } from "@/zustand/authStore";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|
||||||
const logout = useAuthStore((state) => state.logout);
|
const logout = useAuthStore((state) => state.logout);
|
||||||
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid font-sans grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
<div className="grid font-sans grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
|
||||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||||
<Link href={"/auth"}>Login</Link>
|
{!isAuthenticated && <Link href={"/auth"}>Login</Link>}
|
||||||
<Link href={"/"} onClick={logout}>Logout</Link>
|
{isAuthenticated &&
|
||||||
|
<div>
|
||||||
|
<Link href={"/profile"}>Profile</Link>
|
||||||
|
<br />
|
||||||
|
<Link href={""} onClick={logout}>Logout</Link>
|
||||||
|
</div>}
|
||||||
<Image
|
<Image
|
||||||
className="dark:invert"
|
className="dark:invert"
|
||||||
src="/next.svg"
|
src="/next.svg"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { ReactQueryProvider } from '@/components/providers/ReactQueryProvider'
|
||||||
|
|
||||||
|
export default function ProfileLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<ReactQueryProvider>
|
||||||
|
<div className="min-h-screen grid">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ReactQueryProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useProfile } from '@/hooks/auth/useProfile';
|
||||||
|
import { useAuthStore } from '@/zustand/authStore';
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
|
||||||
|
type Props = object
|
||||||
|
|
||||||
|
function ProfileIndex({ }: Props) {
|
||||||
|
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||||
|
|
||||||
|
const { mutate, data, isPending } = useProfile();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
mutate();
|
||||||
|
}
|
||||||
|
}, [isAuthenticated])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isPending && 'Loading...'}
|
||||||
|
<br />
|
||||||
|
{data && JSON.stringify(data)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileIndex
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { checkUserExists } from "@/lib/api/auth"
|
import { checkUserExists } from "@/lib/api/auth/user-exists"
|
||||||
import { useQueryClient } from "@tanstack/react-query"
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
|
|
||||||
export const useCheckUserExistsLazy = () => {
|
export const useCheckUserExistsLazy = () => {
|
||||||
@@ -7,7 +7,7 @@ export const useCheckUserExistsLazy = () => {
|
|||||||
const run = (phone: string) =>
|
const run = (phone: string) =>
|
||||||
queryClient.fetchQuery({
|
queryClient.fetchQuery({
|
||||||
queryKey: ['checkUserExists', phone],
|
queryKey: ['checkUserExists', phone],
|
||||||
queryFn: () => checkUserExists(phone),
|
queryFn: () => checkUserExists({ phone }),
|
||||||
})
|
})
|
||||||
|
|
||||||
return { run }
|
return { run }
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import { login } from '@/lib/api/auth'
|
import { useAuthStore } from '@/zustand/authStore'
|
||||||
import { useAuthStore } from '@/zustand/userStore'
|
import { login } from '@/lib/api/auth/login'
|
||||||
|
|
||||||
export const useLogin = () =>
|
export const useLogin = () =>
|
||||||
useMutation({
|
useMutation({
|
||||||
mutationFn: login,
|
mutationFn: login,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
useAuthStore.getState().login(data.user)
|
useAuthStore.getState().login(data)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { requestOtp } from "@/lib/api/auth/request-otp"
|
||||||
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
|
|
||||||
|
export const useOtpRequest = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const run = (phone: string) =>
|
||||||
|
queryClient.fetchQuery({
|
||||||
|
queryKey: ['requestOtp', phone],
|
||||||
|
queryFn: () => requestOtp({ phone }),
|
||||||
|
})
|
||||||
|
|
||||||
|
return { run }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { OtpValidationRequestModel, validateOtp } from "@/lib/api/auth/validate-otp"
|
||||||
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
|
|
||||||
|
export const useOtpValidation = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const run = (model: OtpValidationRequestModel) =>
|
||||||
|
queryClient.fetchQuery({
|
||||||
|
queryKey: ['requestOtpValidation', model],
|
||||||
|
queryFn: () => validateOtp(model),
|
||||||
|
})
|
||||||
|
|
||||||
|
return { run }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { useMutation } from '@tanstack/react-query'
|
||||||
|
import { useAuthStore } from '@/zustand/authStore'
|
||||||
|
import { fetchMe } from '@/lib/api/auth/me'
|
||||||
|
|
||||||
|
export const useProfile = () =>
|
||||||
|
useMutation({
|
||||||
|
mutationFn: fetchMe,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
useAuthStore.getState().update(data)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { refreshToken } from "@/lib/api/auth/refresh-token"
|
||||||
|
import { useAuthStore } from "@/zustand/authStore"
|
||||||
|
import { useMutation } from "@tanstack/react-query"
|
||||||
|
|
||||||
|
export const useOtpRequest = () => {
|
||||||
|
useMutation({
|
||||||
|
mutationFn: refreshToken,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
useAuthStore.getState().setToken(data.accessToken)
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
useAuthStore.getState().logout();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import { signup } from '@/lib/api/auth'
|
import { signup } from '@/lib/api/auth/signup'
|
||||||
import { useAuthStore } from '@/zustand/userStore'
|
import { useAuthStore } from '@/zustand/authStore'
|
||||||
|
|
||||||
export const useSignup = () =>
|
export const useSignup = () =>
|
||||||
useMutation({
|
useMutation({
|
||||||
mutationFn: signup,
|
mutationFn: signup,
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
useAuthStore.getState().login(data.user)
|
useAuthStore.getState().login(data)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { api } from './axiosInstance'
|
|
||||||
|
|
||||||
export const login = async ({ phone, password }: { phone: string; password: string }) => {
|
|
||||||
const res = await api.post('/login', { phone, password })
|
|
||||||
console.log(res);
|
|
||||||
return res.data // { user: { id, name, email, token } }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const signup = async ({
|
|
||||||
phone,
|
|
||||||
password,
|
|
||||||
}: {
|
|
||||||
phone: string
|
|
||||||
password: string
|
|
||||||
}) => {
|
|
||||||
const res = await api.post('/signup', { phone, password })
|
|
||||||
console.log(res);
|
|
||||||
return res.data
|
|
||||||
}
|
|
||||||
|
|
||||||
export const checkUserExists = async (phone: string) => {
|
|
||||||
const res = await api.get(`/exists/${encodeURIComponent(phone)}`)
|
|
||||||
console.log(res);
|
|
||||||
return res.data.exists as boolean
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type LoginRequestModel = {
|
||||||
|
phone: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const login = async (model: LoginRequestModel) => {
|
||||||
|
const res = await api.post('/login', model)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export const fetchMe = async () => {
|
||||||
|
const res = await api.get('/user')
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type RefreshTokenRequestModel = {
|
||||||
|
token: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const refreshToken = async (model: RefreshTokenRequestModel) => {
|
||||||
|
const res = await api.post('/token', model)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type OtpRequestModel = {
|
||||||
|
phone: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestOtp = async ({ phone }: OtpRequestModel) => {
|
||||||
|
const res = await api.get(`/otp/${encodeURIComponent(phone)}`)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type SignupRequestModel = {
|
||||||
|
phone: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const signup = async (model: SignupRequestModel) => {
|
||||||
|
const res = await api.post('/signup', model)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type CheckUserExistsModel = {
|
||||||
|
phone: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const checkUserExists = async ({ phone }: CheckUserExistsModel) => {
|
||||||
|
const res = await api.get(`/exists/${encodeURIComponent(phone)}`)
|
||||||
|
return res.data.exists as boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { api } from "../axiosInstance";
|
||||||
|
|
||||||
|
export type OtpValidationRequestModel = {
|
||||||
|
phone: string,
|
||||||
|
otp: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateOtp = async (model: OtpValidationRequestModel) => {
|
||||||
|
const res = await api.post(`/otp/check`, model)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
@@ -1,6 +1,106 @@
|
|||||||
import axios from 'axios'
|
import { useAuthStore } from '@/zustand/authStore';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { refreshToken } from './auth/refresh-token';
|
||||||
|
|
||||||
export const api = axios.create({
|
// Create axios instance
|
||||||
|
const api = axios.create({
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com',
|
baseURL: process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
})
|
});
|
||||||
|
|
||||||
|
let isRefreshing = false;
|
||||||
|
let failedQueue: {
|
||||||
|
resolve: (value?: unknown) => void;
|
||||||
|
reject: (error: unknown) => void;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
const processQueue = (error: unknown, token: string | null = null) => {
|
||||||
|
failedQueue.forEach(({ resolve, reject }) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(token);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
failedQueue = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach access token to requests
|
||||||
|
api.interceptors.request.use((config) => {
|
||||||
|
const state = useAuthStore.getState();
|
||||||
|
console.log('AuthStore state:', state);
|
||||||
|
|
||||||
|
const token = state.user?.accessToken;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
config.headers = config.headers || {};
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Response interceptor to handle 401 and refresh token logic
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
async (error) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
if (
|
||||||
|
error.response?.status === 401 &&
|
||||||
|
!originalRequest._retry // custom flag to prevent infinite loops
|
||||||
|
) {
|
||||||
|
if (isRefreshing) {
|
||||||
|
// Queue the requests while refreshing
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
failedQueue.push({ resolve, reject });
|
||||||
|
})
|
||||||
|
.then((token) => {
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||||
|
return api(originalRequest);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
originalRequest._retry = true;
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
|
const token = useAuthStore.getState().user?.refreshToken;
|
||||||
|
console.log(useAuthStore.getState())
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
|
||||||
|
useAuthStore.getState().logout();
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await refreshToken({ token });
|
||||||
|
// Update tokens in Zustand store
|
||||||
|
useAuthStore.getState().login({
|
||||||
|
...useAuthStore.getState().user!,
|
||||||
|
accessToken: data.accessToken,
|
||||||
|
refreshToken: data.refreshToken || token,
|
||||||
|
});
|
||||||
|
|
||||||
|
api.defaults.headers.common.Authorization = `Bearer ${data.accessToken}`;
|
||||||
|
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
|
||||||
|
|
||||||
|
processQueue(null, data.accessToken);
|
||||||
|
|
||||||
|
return api(originalRequest);
|
||||||
|
} catch (err) {
|
||||||
|
processQueue(err, null);
|
||||||
|
useAuthStore.getState().logout();
|
||||||
|
return Promise.reject(err);
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export { api };
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ type User = {
|
|||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
number: string
|
number: string
|
||||||
token: string
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthStore = {
|
type AuthStore = {
|
||||||
@@ -14,6 +15,8 @@ type AuthStore = {
|
|||||||
isAuthenticated: boolean
|
isAuthenticated: boolean
|
||||||
login: (userData: User) => void
|
login: (userData: User) => void
|
||||||
logout: () => void
|
logout: () => void
|
||||||
|
update: (userData: User) => void
|
||||||
|
setToken: (token: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = create<AuthStore>()(
|
export const useAuthStore = create<AuthStore>()(
|
||||||
@@ -25,12 +28,35 @@ export const useAuthStore = create<AuthStore>()(
|
|||||||
set(() => ({
|
set(() => ({
|
||||||
user: userData,
|
user: userData,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
})),
|
}))
|
||||||
|
,
|
||||||
logout: () =>
|
logout: () =>
|
||||||
set(() => ({
|
set(() => ({
|
||||||
user: null,
|
user: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
})),
|
})),
|
||||||
|
update: (userData) => {
|
||||||
|
set((state) => {
|
||||||
|
if (!state.user) return state; // no user to update
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
...userData
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setToken: (token) => {
|
||||||
|
set((state) => {
|
||||||
|
if (!state.user) return state; // no user to update
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
...state.user,
|
||||||
|
accessToken: token ?? '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'auth-storage', // Key in localStorage
|
name: 'auth-storage', // Key in localStorage
|
||||||
Reference in New Issue
Block a user