Skip to content

Commit aa8a504

Browse files
committed
refactor: update authentication logic and integrate Axios for API requests - Commented out unused code in auth.ts for clarity - Added Axios dependency for improved API client implementation - Refactored API client to use Axios instead of fetch for better error handling and request configuration - Updated button styles for consistency across the UI
1 parent c8d38ee commit aa8a504

6 files changed

Lines changed: 150 additions & 107 deletions

File tree

auth.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import NextAuth from 'next-auth';
22
import Credentials from 'next-auth/providers/credentials';
33
import { login, getMe as getMeBase } from '@/lib/api/auth';
44
import Google from 'next-auth/providers/google';
5-
import Cookies from 'js-cookie';
5+
// import Cookies from 'js-cookie';
66

77
function safeRole(val: unknown): 'USER' | 'ADMIN' {
88
return val === 'ADMIN' ? 'ADMIN' : 'USER';
@@ -125,26 +125,26 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
125125
const response = await login({ email, password });
126126

127127
if (response && response.accessToken) {
128-
const user = await getMe(response.accessToken);
129-
130-
if (user) {
131-
if (user.isVerified === false) {
132-
throw new Error('UNVERIFIED_EMAIL');
133-
}
134-
135-
const userInfo = extractUserInfo(user);
136-
Cookies.set('accessToken', response.accessToken);
137-
Cookies.set('refreshToken', response.refreshToken || '');
138-
139-
return {
140-
...userInfo,
141-
accessToken: response.accessToken,
142-
refreshToken: response.refreshToken,
143-
};
144-
}
128+
// const user = await getMe(response.accessToken);
129+
// if (user) {
130+
// if (user.isVerified === false) {
131+
// throw new Error('UNVERIFIED_EMAIL');
132+
// }
133+
134+
// const userInfo = extractUserInfo(user);
135+
// Cookies.set('accessToken', response.accessToken);
136+
// Cookies.set('refreshToken', response.refreshToken || '');
137+
138+
return {
139+
// ...userInfo,
140+
accessToken: response.accessToken,
141+
refreshToken: response.refreshToken,
142+
};
143+
// }
145144
}
146145
return null;
147146
} catch (err) {
147+
console.log('err', err);
148148
if (err instanceof Error && err.message === 'UNVERIFIED_EMAIL') {
149149
throw err;
150150
}

components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const buttonVariants = cva(
1414
destructive:
1515
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
1616
outline:
17-
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
17+
'border bg-[#EFEFEF] shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
1818
secondary:
1919
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
2020
ghost:

lib/api/api.ts

Lines changed: 122 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
// Environment validation
1+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2+
import Cookies from 'js-cookie';
3+
24
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;
35
if (!API_BASE_URL) {
46
throw new Error(
57
'NEXT_PUBLIC_API_BASE_URL environment variable is not defined'
68
);
79
}
810

9-
// Types for API responses
1011
export interface ApiResponse<T = unknown> {
1112
data: T;
1213
status: number;
@@ -19,110 +20,144 @@ export interface ApiError {
1920
code?: string;
2021
}
2122

22-
// Request configuration interface
2323
export interface RequestConfig {
2424
headers?: Record<string, string>;
2525
timeout?: number;
2626
}
2727

28-
// Create fetch-based API client
29-
const createClientApi = () => {
30-
const baseURL = API_BASE_URL;
31-
32-
const createRequestConfig = (config?: RequestConfig): RequestInit => ({
33-
credentials: 'include', // Important: enables sending HTTP-only cookies
28+
const createClientApi = (): AxiosInstance => {
29+
const instance = axios.create({
30+
baseURL: API_BASE_URL,
31+
timeout: 10000,
3432
headers: {
3533
'Content-Type': 'application/json',
3634
Accept: 'application/json',
37-
...config?.headers,
3835
},
36+
withCredentials: true,
3937
});
4038

41-
const handleResponse = async <T>(
42-
response: Response
43-
): Promise<ApiResponse<T>> => {
44-
if (!response.ok) {
45-
const errorData = await response.json().catch(() => ({}));
46-
throw new Error(
47-
errorData.message || `HTTP error! status: ${response.status}`
48-
);
39+
instance.interceptors.request.use(
40+
config => {
41+
config.withCredentials = true;
42+
43+
const accessToken = Cookies.get('accessToken');
44+
if (accessToken && !config.headers?.Authorization) {
45+
config.headers = config.headers || {};
46+
config.headers.Authorization = `Bearer ${accessToken}`;
47+
}
48+
49+
return config;
50+
},
51+
error => {
52+
return Promise.reject(error);
4953
}
54+
);
5055

51-
const data = await response.json();
52-
return {
56+
instance.interceptors.response.use(
57+
(response: AxiosResponse) => {
58+
return response;
59+
},
60+
error => {
61+
if (error.response) {
62+
const errorData = error.response.data;
63+
const customError: ApiError = {
64+
message:
65+
errorData?.message ||
66+
`HTTP error! status: ${error.response.status}`,
67+
status: error.response.status,
68+
code: errorData?.code,
69+
};
70+
return Promise.reject(customError);
71+
} else if (error.request) {
72+
return Promise.reject(new Error('Network error: No response received'));
73+
} else {
74+
return Promise.reject(new Error(`Request error: ${error.message}`));
75+
}
76+
}
77+
);
78+
79+
return instance;
80+
};
81+
82+
const axiosInstance = createClientApi();
83+
84+
const convertAxiosResponse = <T>(
85+
response: AxiosResponse<T>
86+
): ApiResponse<T> => ({
87+
data: response.data,
88+
status: response.status,
89+
statusText: response.statusText,
90+
});
91+
92+
const convertRequestConfig = (config?: RequestConfig): AxiosRequestConfig => ({
93+
headers: config?.headers,
94+
timeout: config?.timeout,
95+
withCredentials: true,
96+
});
97+
98+
const clientApi = {
99+
get: async <T = unknown>(
100+
url: string,
101+
config?: RequestConfig
102+
): Promise<ApiResponse<T>> => {
103+
const response = await axiosInstance.get<T>(
104+
url,
105+
convertRequestConfig(config)
106+
);
107+
return convertAxiosResponse(response);
108+
},
109+
110+
post: async <T = unknown>(
111+
url: string,
112+
data?: unknown,
113+
config?: RequestConfig
114+
): Promise<ApiResponse<T>> => {
115+
const response = await axiosInstance.post<T>(
116+
url,
53117
data,
54-
status: response.status,
55-
statusText: response.statusText,
56-
};
57-
};
118+
convertRequestConfig(config)
119+
);
120+
return convertAxiosResponse(response);
121+
},
58122

59-
const makeRequest = async <T>(
123+
put: async <T = unknown>(
60124
url: string,
61-
options: RequestInit & { config?: RequestConfig } = {}
125+
data?: unknown,
126+
config?: RequestConfig
62127
): Promise<ApiResponse<T>> => {
63-
const { config, ...requestOptions } = options;
64-
const fullUrl = url.startsWith('http') ? url : `${baseURL}${url}`;
65-
66-
const response = await fetch(fullUrl, {
67-
...createRequestConfig(config),
68-
...requestOptions,
69-
});
70-
71-
return handleResponse<T>(response);
72-
};
73-
74-
return {
75-
get: <T = unknown>(
76-
url: string,
77-
config?: RequestConfig
78-
): Promise<ApiResponse<T>> =>
79-
makeRequest<T>(url, { method: 'GET', config }),
80-
81-
post: <T = unknown>(
82-
url: string,
83-
data?: unknown,
84-
config?: RequestConfig
85-
): Promise<ApiResponse<T>> =>
86-
makeRequest<T>(url, {
87-
method: 'POST',
88-
body: data ? JSON.stringify(data) : undefined,
89-
config,
90-
}),
91-
92-
put: <T = unknown>(
93-
url: string,
94-
data?: unknown,
95-
config?: RequestConfig
96-
): Promise<ApiResponse<T>> =>
97-
makeRequest<T>(url, {
98-
method: 'PUT',
99-
body: data ? JSON.stringify(data) : undefined,
100-
config,
101-
}),
102-
103-
patch: <T = unknown>(
104-
url: string,
105-
data?: unknown,
106-
config?: RequestConfig
107-
): Promise<ApiResponse<T>> =>
108-
makeRequest<T>(url, {
109-
method: 'PATCH',
110-
body: data ? JSON.stringify(data) : undefined,
111-
config,
112-
}),
113-
114-
delete: <T = unknown>(
115-
url: string,
116-
config?: RequestConfig
117-
): Promise<ApiResponse<T>> =>
118-
makeRequest<T>(url, { method: 'DELETE', config }),
119-
};
120-
};
128+
const response = await axiosInstance.put<T>(
129+
url,
130+
data,
131+
convertRequestConfig(config)
132+
);
133+
return convertAxiosResponse(response);
134+
},
135+
136+
patch: async <T = unknown>(
137+
url: string,
138+
data?: unknown,
139+
config?: RequestConfig
140+
): Promise<ApiResponse<T>> => {
141+
const response = await axiosInstance.patch<T>(
142+
url,
143+
data,
144+
convertRequestConfig(config)
145+
);
146+
return convertAxiosResponse(response);
147+
},
121148

122-
// Create and export the client API instance
123-
const clientApi = createClientApi();
149+
delete: async <T = unknown>(
150+
url: string,
151+
config?: RequestConfig
152+
): Promise<ApiResponse<T>> => {
153+
const response = await axiosInstance.delete<T>(
154+
url,
155+
convertRequestConfig(config)
156+
);
157+
return convertAxiosResponse(response);
158+
},
159+
};
124160

125-
// Utility functions for common HTTP methods
126161
export const api = {
127162
get: <T = unknown>(
128163
url: string,

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@radix-ui/react-tooltip": "^1.2.7",
5454
"@stellar/freighter-api": "^4.1.0",
5555
"@stellar/stellar-sdk": "^13.3.0",
56+
"axios": "^1.11.0",
5657
"class-variance-authority": "^0.7.1",
5758
"clsx": "^2.1.1",
5859
"cmdk": "^1.1.1",

tsconfig.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
"@/*": ["./*"]
2323
}
2424
},
25-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25+
"include": [
26+
"**/*.ts",
27+
"**/*.tsx",
28+
".next/types/**/*.ts",
29+
"next-env.d.ts",
30+
"./.next/types/**/*.ts"
31+
],
2632
"exclude": ["node_modules"]
2733
}

0 commit comments

Comments
 (0)