mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
refactor: centralize API endpoints and token refresh logic
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@@ -1,23 +1,50 @@
|
||||
import axios, { type AxiosError } from "axios";
|
||||
import { endpoints } from "../config/endpoints";
|
||||
import { useAuth } from "../store/useAuth";
|
||||
|
||||
const authApiClient = axios.create({
|
||||
baseURL: `${import.meta.env.VITE_API_URL}/api/auth/`,
|
||||
const baseURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
export const preAuthApiClient = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
authApiClient.interceptors.response.use(
|
||||
export const postAuthApiClient = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
// automatically attach access token to requests
|
||||
postAuthApiClient.interceptors.request.use((config) => {
|
||||
const token = useAuth.getState().accessToken;
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// handle 401 & auto token refresh
|
||||
postAuthApiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// do not retry refresh request
|
||||
if (
|
||||
error.response?.status === 401 &&
|
||||
!error.config?.url?.includes("refresh/")
|
||||
originalRequest &&
|
||||
!originalRequest.url?.includes(endpoints.REFRESH)
|
||||
) {
|
||||
try {
|
||||
const response = await authApiClient.post("refresh/");
|
||||
// refresh the access token
|
||||
const response = await preAuthApiClient.post(endpoints.REFRESH);
|
||||
|
||||
if (response.status === 200) {
|
||||
const newAccessToken = response.data.access;
|
||||
|
||||
@@ -26,26 +53,16 @@ authApiClient.interceptors.response.use(
|
||||
accessToken: newAccessToken,
|
||||
isAuthenticated: true,
|
||||
});
|
||||
if (error.config) {
|
||||
error.config.headers.Authorization = `Bearer ${newAccessToken}`;
|
||||
return authApiClient(error.config);
|
||||
}
|
||||
|
||||
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
||||
return postAuthApiClient(originalRequest);
|
||||
}
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
} catch (refreshError) {
|
||||
useAuth.getState().logout();
|
||||
return Promise.reject(refreshError);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
// automatically attach access token to request
|
||||
authApiClient.interceptors.request.use((config) => {
|
||||
const token = useAuth.getState().accessToken;
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
export default authApiClient;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export const endpoints = {
|
||||
LOGIN: "/api/auth/login/",
|
||||
REGISTER: "/api/auth/register/",
|
||||
VERIFY_EMAIL: "/api/auth/verify-email/",
|
||||
ACTIVATE: "/api/auth/activate/:uidb64/:token/",
|
||||
ME: "/api/auth/me/",
|
||||
REFRESH: "/api/auth/refresh/",
|
||||
LOGOUT: "/api/auth/logout/",
|
||||
};
|
||||
|
||||
// simple utility to handle path params
|
||||
export const replacePathParams = (
|
||||
url: string,
|
||||
params: Record<string, string>,
|
||||
): string => {
|
||||
let result = url;
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
result = result.replace(`:${key}`, value);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
@@ -1,8 +1,10 @@
|
||||
import { CheckCircleIcon, XCircleIcon } from "@phosphor-icons/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import authApiClient from "../api/apiClient";
|
||||
import { preAuthApiClient } from "../api/apiClient";
|
||||
import Logo from "../components/Logo";
|
||||
import { endpoints, replacePathParams } from "../config/endpoints";
|
||||
import { ROUTES } from "../config/routes";
|
||||
|
||||
export default function Activate() {
|
||||
const { uidb64, token } = useParams();
|
||||
@@ -20,7 +22,11 @@ export default function Activate() {
|
||||
|
||||
const activateAccount = async () => {
|
||||
try {
|
||||
await authApiClient.get(`/activate/${uidb64}/${token}/`);
|
||||
const url = replacePathParams(endpoints.ACTIVATE, {
|
||||
uidb64,
|
||||
token,
|
||||
});
|
||||
await preAuthApiClient.get(url);
|
||||
setStatus("success");
|
||||
} catch (err) {
|
||||
console.error("Activation error:", err);
|
||||
@@ -80,7 +86,7 @@ export default function Activate() {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost w-full"
|
||||
onClick={() => navigate("/onboard")}
|
||||
onClick={() => navigate(ROUTES.ONBOARD)}
|
||||
>
|
||||
Back to Registration
|
||||
</button>
|
||||
|
||||
@@ -5,9 +5,10 @@ import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { z } from "zod";
|
||||
import authApiClient from "../api/apiClient";
|
||||
import { preAuthApiClient } from "../api/apiClient";
|
||||
import Logo from "../components/Logo";
|
||||
import FormField from "../components/ui/FormField";
|
||||
import { endpoints } from "../config/endpoints";
|
||||
|
||||
// validation logic
|
||||
const registerSchema = z
|
||||
@@ -41,7 +42,7 @@ export default function Register() {
|
||||
setIsLoading(true);
|
||||
setApiError(null);
|
||||
try {
|
||||
await authApiClient.post("/register/", {
|
||||
await preAuthApiClient.post(endpoints.REGISTER, {
|
||||
full_name: data.full_name,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { create } from "zustand";
|
||||
import authApiClient from "../api/apiClient";
|
||||
import { postAuthApiClient, preAuthApiClient } from "../api/apiClient";
|
||||
import { endpoints } from "../config/endpoints";
|
||||
|
||||
interface UserProfile {
|
||||
public_id: string;
|
||||
@@ -17,14 +18,14 @@ interface AuthState {
|
||||
checkAuth: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuth = create<AuthState>((set) => ({
|
||||
export const useAuth = create<AuthState>((set, get) => ({
|
||||
accessToken: null,
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
isInitializing: true,
|
||||
|
||||
login: async (credentials: any) => {
|
||||
const response = await authApiClient.post("login/", credentials);
|
||||
const response = await preAuthApiClient.post(endpoints.LOGIN, credentials);
|
||||
set({
|
||||
accessToken: response.data.access,
|
||||
isAuthenticated: true,
|
||||
@@ -34,7 +35,10 @@ export const useAuth = create<AuthState>((set) => ({
|
||||
|
||||
logout: async () => {
|
||||
try {
|
||||
await authApiClient.post("logout/");
|
||||
const token = get().accessToken;
|
||||
if (token) {
|
||||
await preAuthApiClient.post(endpoints.LOGOUT);
|
||||
}
|
||||
} finally {
|
||||
set({
|
||||
accessToken: null,
|
||||
@@ -46,7 +50,7 @@ export const useAuth = create<AuthState>((set) => ({
|
||||
|
||||
checkAuth: async () => {
|
||||
try {
|
||||
const response = await authApiClient.get("me/");
|
||||
const response = await postAuthApiClient.get(endpoints.ME);
|
||||
set({
|
||||
user: response.data,
|
||||
isAuthenticated: true,
|
||||
|
||||
Reference in New Issue
Block a user