add: full auth functionality

add: zustand auth store
add: jwt auto refresh
This commit is contained in:
Mahyar Khanbolooki
2025-07-01 22:49:56 +03:30
parent ef63706116
commit 48c458f012
22 changed files with 340 additions and 53 deletions
-25
View File
@@ -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
}
+11
View File
@@ -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
}
+6
View File
@@ -0,0 +1,6 @@
import { api } from "../axiosInstance";
export const fetchMe = async () => {
const res = await api.get('/user')
return res.data
}
+10
View File
@@ -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
}
+10
View File
@@ -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
}
+11
View File
@@ -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
}
+10
View File
@@ -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
}
+11
View File
@@ -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
}
+103 -3
View File
@@ -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',
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 };