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 axios, { type AxiosError } from "axios";
|
||||||
|
import { endpoints } from "../config/endpoints";
|
||||||
import { useAuth } from "../store/useAuth";
|
import { useAuth } from "../store/useAuth";
|
||||||
|
|
||||||
const authApiClient = axios.create({
|
const baseURL = import.meta.env.VITE_API_URL;
|
||||||
baseURL: `${import.meta.env.VITE_API_URL}/api/auth/`,
|
|
||||||
|
export const preAuthApiClient = axios.create({
|
||||||
|
baseURL,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"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,
|
(response) => response,
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
|
const originalRequest = error.config;
|
||||||
|
|
||||||
|
// do not retry refresh request
|
||||||
if (
|
if (
|
||||||
error.response?.status === 401 &&
|
error.response?.status === 401 &&
|
||||||
!error.config?.url?.includes("refresh/")
|
originalRequest &&
|
||||||
|
!originalRequest.url?.includes(endpoints.REFRESH)
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const response = await authApiClient.post("refresh/");
|
// refresh the access token
|
||||||
|
const response = await preAuthApiClient.post(endpoints.REFRESH);
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const newAccessToken = response.data.access;
|
const newAccessToken = response.data.access;
|
||||||
|
|
||||||
@@ -26,26 +53,16 @@ authApiClient.interceptors.response.use(
|
|||||||
accessToken: newAccessToken,
|
accessToken: newAccessToken,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
});
|
});
|
||||||
if (error.config) {
|
|
||||||
error.config.headers.Authorization = `Bearer ${newAccessToken}`;
|
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
||||||
return authApiClient(error.config);
|
return postAuthApiClient(originalRequest);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (refreshError) {
|
||||||
return Promise.reject(error);
|
useAuth.getState().logout();
|
||||||
|
return Promise.reject(refreshError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
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 { CheckCircleIcon, XCircleIcon } from "@phosphor-icons/react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import authApiClient from "../api/apiClient";
|
import { preAuthApiClient } from "../api/apiClient";
|
||||||
import Logo from "../components/Logo";
|
import Logo from "../components/Logo";
|
||||||
|
import { endpoints, replacePathParams } from "../config/endpoints";
|
||||||
|
import { ROUTES } from "../config/routes";
|
||||||
|
|
||||||
export default function Activate() {
|
export default function Activate() {
|
||||||
const { uidb64, token } = useParams();
|
const { uidb64, token } = useParams();
|
||||||
@@ -20,7 +22,11 @@ export default function Activate() {
|
|||||||
|
|
||||||
const activateAccount = async () => {
|
const activateAccount = async () => {
|
||||||
try {
|
try {
|
||||||
await authApiClient.get(`/activate/${uidb64}/${token}/`);
|
const url = replacePathParams(endpoints.ACTIVATE, {
|
||||||
|
uidb64,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
await preAuthApiClient.get(url);
|
||||||
setStatus("success");
|
setStatus("success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Activation error:", err);
|
console.error("Activation error:", err);
|
||||||
@@ -80,7 +86,7 @@ export default function Activate() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-ghost w-full"
|
className="btn btn-ghost w-full"
|
||||||
onClick={() => navigate("/onboard")}
|
onClick={() => navigate(ROUTES.ONBOARD)}
|
||||||
>
|
>
|
||||||
Back to Registration
|
Back to Registration
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { useState } from "react";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import authApiClient from "../api/apiClient";
|
import { preAuthApiClient } from "../api/apiClient";
|
||||||
import Logo from "../components/Logo";
|
import Logo from "../components/Logo";
|
||||||
import FormField from "../components/ui/FormField";
|
import FormField from "../components/ui/FormField";
|
||||||
|
import { endpoints } from "../config/endpoints";
|
||||||
|
|
||||||
// validation logic
|
// validation logic
|
||||||
const registerSchema = z
|
const registerSchema = z
|
||||||
@@ -41,7 +42,7 @@ export default function Register() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setApiError(null);
|
setApiError(null);
|
||||||
try {
|
try {
|
||||||
await authApiClient.post("/register/", {
|
await preAuthApiClient.post(endpoints.REGISTER, {
|
||||||
full_name: data.full_name,
|
full_name: data.full_name,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
password: data.password,
|
password: data.password,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import authApiClient from "../api/apiClient";
|
import { postAuthApiClient, preAuthApiClient } from "../api/apiClient";
|
||||||
|
import { endpoints } from "../config/endpoints";
|
||||||
|
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
public_id: string;
|
public_id: string;
|
||||||
@@ -17,14 +18,14 @@ interface AuthState {
|
|||||||
checkAuth: () => Promise<void>;
|
checkAuth: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuth = create<AuthState>((set) => ({
|
export const useAuth = create<AuthState>((set, get) => ({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
user: null,
|
user: null,
|
||||||
isInitializing: true,
|
isInitializing: true,
|
||||||
|
|
||||||
login: async (credentials: any) => {
|
login: async (credentials: any) => {
|
||||||
const response = await authApiClient.post("login/", credentials);
|
const response = await preAuthApiClient.post(endpoints.LOGIN, credentials);
|
||||||
set({
|
set({
|
||||||
accessToken: response.data.access,
|
accessToken: response.data.access,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
@@ -34,7 +35,10 @@ export const useAuth = create<AuthState>((set) => ({
|
|||||||
|
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
try {
|
try {
|
||||||
await authApiClient.post("logout/");
|
const token = get().accessToken;
|
||||||
|
if (token) {
|
||||||
|
await preAuthApiClient.post(endpoints.LOGOUT);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
set({
|
set({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
@@ -46,7 +50,7 @@ export const useAuth = create<AuthState>((set) => ({
|
|||||||
|
|
||||||
checkAuth: async () => {
|
checkAuth: async () => {
|
||||||
try {
|
try {
|
||||||
const response = await authApiClient.get("me/");
|
const response = await postAuthApiClient.get(endpoints.ME);
|
||||||
set({
|
set({
|
||||||
user: response.data,
|
user: response.data,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user