Skip to content

Commit 0808534

Browse files
Version: 0.0.2
1 parent c44113f commit 0808534

File tree

90 files changed

+6786
-5479
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+6786
-5479
lines changed

package-lock.json

Lines changed: 43 additions & 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
@@ -46,6 +46,7 @@
4646
"clsx": "^2.1.1",
4747
"cmdk": "^1.1.1",
4848
"date-fns": "^4.1.0",
49+
"framer-motion": "^12.23.0",
4950
"heroicons": "^2.2.0",
5051
"jwt-decode": "^4.0.0",
5152
"lucide-react": "^0.511.0",

src/App.css

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@
7777
@apply bg-background text-foreground;
7878
font-feature-settings: "rlig" 1, "calt" 1;
7979
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
80+
/* Prevent horizontal scroll on mobile */
81+
overflow-x: hidden;
82+
/* Improve touch scrolling on mobile */
83+
-webkit-overflow-scrolling: touch;
8084
}
8185

8286
/* Custom scrollbar */
@@ -95,6 +99,39 @@
9599
::-webkit-scrollbar-thumb:hover {
96100
@apply bg-muted-foreground/50;
97101
}
102+
103+
/* Mobile-specific improvements */
104+
@media (max-width: 768px) {
105+
/* Improve touch targets */
106+
button, [role="button"] {
107+
min-height: 44px;
108+
min-width: 44px;
109+
}
110+
111+
/* Improve text readability on mobile */
112+
p, span, div {
113+
line-height: 1.5;
114+
}
115+
116+
/* Prevent zoom on input focus on iOS */
117+
input, textarea, select {
118+
font-size: 16px;
119+
}
120+
121+
/* Improve card shadows on mobile */
122+
.card {
123+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
124+
}
125+
}
126+
127+
/* Tablet-specific improvements */
128+
@media (min-width: 769px) and (max-width: 1024px) {
129+
/* Optimize spacing for tablets */
130+
.container {
131+
padding-left: 1rem;
132+
padding-right: 1rem;
133+
}
134+
}
98135
}
99136

100137
@layer components {
@@ -109,6 +146,32 @@
109146
.glass-effect {
110147
@apply bg-background/80 backdrop-blur-sm border border-border/50;
111148
}
149+
150+
/* Mobile-first responsive utilities */
151+
.mobile-container {
152+
@apply px-3 sm:px-4 md:px-6 lg:px-8;
153+
}
154+
155+
.mobile-text {
156+
@apply text-xs sm:text-sm md:text-base;
157+
}
158+
159+
.mobile-padding {
160+
@apply p-3 sm:p-4 md:p-6;
161+
}
162+
163+
.mobile-gap {
164+
@apply gap-2 sm:gap-3 md:gap-4;
165+
}
166+
167+
/* Responsive grid utilities */
168+
.responsive-grid {
169+
@apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4;
170+
}
171+
172+
.responsive-card-grid {
173+
@apply grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3;
174+
}
112175
}
113176

114177
@keyframes fadeIn {

src/api/core/baseApi.js

Lines changed: 171 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,168 @@
11
import { createApi, fetchBaseQuery, retry } from "@reduxjs/toolkit/query/react";
22
import { API_CONFIG, getBaseUrl } from '../config/config';
33
import { handleApiResponse } from '../config/errors';
4-
import { refreshTokens } from './tokenManager';
4+
import { refreshTokens, setLoggingOut } from './tokenManager';
55
import { getToken } from './tokenManager';
6+
import { logOut } from '../../state/data/authSlice';
67

7-
// Create base query with automatic token refresh
8+
// Cache configuration for different types of data
9+
export const CACHE_TIMES = {
10+
SHORT: 5 * 60, // 5 minutes
11+
MEDIUM: 15 * 60, // 15 minutes
12+
LONG: 60 * 60, // 1 hour
13+
VERY_LONG: 24 * 60 * 60, // 24 hours
14+
};
15+
16+
// Performance monitoring for API calls
17+
export const performanceTracker = {
18+
calls: [],
19+
addCall: (endpoint, duration, status) => {
20+
const call = {
21+
endpoint,
22+
duration,
23+
status,
24+
timestamp: new Date().toISOString(),
25+
};
26+
27+
// Keep only last 100 calls
28+
performanceTracker.calls.push(call);
29+
if (performanceTracker.calls.length > 100) {
30+
performanceTracker.calls = performanceTracker.calls.slice(-100);
31+
}
32+
33+
// Warn about slow API calls
34+
if (duration > 2000) {
35+
console.warn(`Slow API call detected: ${endpoint} took ${duration}ms`);
36+
}
37+
},
38+
getStats: () => {
39+
const calls = performanceTracker.calls;
40+
if (calls.length === 0) return null;
41+
42+
const totalCalls = calls.length;
43+
const avgDuration = calls.reduce((sum, call) => sum + call.duration, 0) / totalCalls;
44+
const slowCalls = calls.filter((call) => call.duration > 1000).length;
45+
const errorCalls = calls.filter((call) => call.status >= 400).length;
46+
47+
return {
48+
totalCalls,
49+
avgDuration: Math.round(avgDuration),
50+
slowCalls,
51+
errorCalls,
52+
errorRate: Math.round((errorCalls / totalCalls) * 100),
53+
};
54+
},
55+
};
56+
57+
// Track app initialization to prevent premature logout
58+
let appInitialized = false;
59+
setTimeout(() => { appInitialized = true; }, 2000);
60+
61+
// Enhanced base query with performance tracking
862
const baseQuery = fetchBaseQuery({
963
baseUrl: getBaseUrl(),
1064
timeout: API_CONFIG.REQUEST.TIMEOUT,
11-
prepareHeaders: (headers) => {
12-
const token = getToken();
13-
if (token) {
14-
headers.set("authorization", `Bearer ${token}`);
65+
prepareHeaders: (headers, { getState, endpoint }) => {
66+
const isAuthEndpoint = endpoint?.includes('auth/');
67+
68+
if (!isAuthEndpoint) {
69+
const token = getToken();
70+
if (token) {
71+
headers.set("authorization", `Bearer ${token}`);
72+
}
1573
}
74+
1675
headers.set("content-type", "application/json");
76+
headers.set("X-Client-Version", "2.0");
77+
headers.set("X-Request-Time", Date.now().toString());
1778
return headers;
1879
},
80+
// Enhanced fetch with performance tracking
81+
fetchFn: async (input, init) => {
82+
const startTime = performance.now();
83+
84+
try {
85+
const response = await fetch(input, {
86+
...init,
87+
signal: AbortSignal.timeout(API_CONFIG.REQUEST.TIMEOUT || 30000),
88+
});
89+
90+
const endTime = performance.now();
91+
const duration = endTime - startTime;
92+
93+
// Track performance
94+
const endpoint = typeof input === "string" ? input : input.url;
95+
performanceTracker.addCall(endpoint, duration, response.status);
96+
97+
return response;
98+
} catch (error) {
99+
const endTime = performance.now();
100+
const duration = endTime - startTime;
101+
102+
// Track failed requests
103+
const endpoint = typeof input === "string" ? input : input.url;
104+
performanceTracker.addCall(endpoint, duration, 0);
105+
106+
throw error;
107+
}
108+
},
19109
});
20110

21-
// Add retry logic with token refresh
111+
// Enhanced retry logic with token refresh and better error handling
22112
const baseQueryWithRetry = retry(
23113
async (args, api, extraOptions) => {
24114
let result = await baseQuery(args, api, extraOptions);
25115

26-
if (result.error && result.error.status === 401) {
27-
// Try to get a new token
28-
try {
29-
const refreshResult = await refreshTokens(api);
30-
if (refreshResult) {
31-
// Retry the initial query
116+
// Handle authentication errors with automatic refresh
117+
if (result.error?.status === 401) {
118+
const state = api.getState();
119+
const hasToken = state?.auth?.accessToken;
120+
const refreshToken = state?.auth?.refreshToken;
121+
const isAuthEndpoint = args.url?.includes('/auth/') || args.url?.includes('/login') || args.url?.includes('/register');
122+
const isRefreshEndpoint = args.url?.includes('/refresh');
123+
124+
// Only attempt refresh if we have tokens, not an auth endpoint, and app is initialized
125+
if (hasToken && refreshToken && !isAuthEndpoint && !isRefreshEndpoint && appInitialized) {
126+
try {
127+
await refreshTokens(api);
128+
// Retry the original request with the new token
32129
result = await baseQuery(args, api, extraOptions);
130+
} catch (refreshError) {
131+
console.error('Token refresh failed:', refreshError);
132+
133+
// Only logout if refresh token is invalid, not for network errors
134+
if (refreshError.message?.includes('Refresh failed with status: 401')) {
135+
api.dispatch(logOut());
136+
137+
// Redirect to login if not already there
138+
if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register')) {
139+
setTimeout(() => {
140+
window.location.href = '/login';
141+
}, 100);
142+
}
143+
}
144+
}
145+
} else if (!hasToken || !refreshToken) {
146+
// No tokens available, logout
147+
if (appInitialized) {
148+
api.dispatch(logOut());
149+
if (!window.location.pathname.includes('/login') && !window.location.pathname.includes('/register')) {
150+
setTimeout(() => {
151+
window.location.href = '/login';
152+
}, 100);
153+
}
33154
}
34-
} catch (error) {
35-
console.error('Token refresh failed:', error);
155+
}
156+
}
157+
158+
// Enhanced error logging for non-auth errors
159+
if (result.error && result.error.status !== 401) {
160+
if (result.error.status === 404) {
161+
console.warn(`API endpoint not found: ${args.url}`);
162+
} else if (result.error.status >= 500) {
163+
console.error(`Server error (${result.error.status}) for ${args.url}:`, result.error);
164+
} else {
165+
console.warn(`API error (${result.error.status}) for ${args.url}:`, result.error);
36166
}
37167
}
38168

@@ -50,4 +180,29 @@ export const createBaseApi = (options) => {
50180
endpoints: () => ({}),
51181
...options,
52182
});
53-
};
183+
};
184+
185+
// Create the main base API instance
186+
export const baseApi = createApi({
187+
reducerPath: "baseApi",
188+
baseQuery: baseQueryWithRetry,
189+
tagTypes: [
190+
"Auth",
191+
"User",
192+
"Report",
193+
"Appointment",
194+
"Chat",
195+
"Blog",
196+
"ClinicalNote",
197+
"Vitals",
198+
],
199+
endpoints: () => ({}),
200+
});
201+
202+
// Export the enhanced base query for backward compatibility
203+
export const baseQueryWithReauth = baseQueryWithRetry;
204+
205+
// Export token management utilities
206+
export { setLoggingOut, startPeriodicTokenCheck, stopPeriodicTokenCheck } from './tokenManager';
207+
208+
export default baseApi;

src/api/core/tokenManager.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export const startPeriodicTokenCheck = (api) => {
9797
// Check every 5 minutes
9898
tokenCheckInterval = setInterval(async () => {
9999
try {
100-
// Don't check if user is logging out
101-
if (isLoggingOut) {
100+
// Don't check if user is logging out or app is not ready
101+
if (isLoggingOut || !api?.getState) {
102102
return;
103103
}
104104

0 commit comments

Comments
 (0)