Skip to content

Commit e329159

Browse files
Manage user page, add user, edit user and user profile (#61)
* new landing page * issue 3,4,47 * made few ui changes , added translations * API implementaion and UI changes for list,add,update users * Added user profile page and edit option * fiexed users table scrollable issue , controlled access to users list page * Cleaning up code base and adding loading screen while fetching user details * Updating the font family to Inter font * Updating the user status on login * fixing CSP issue and added translation * added logger and cleaned up * Updated the profile edit logic and removed dead code * Enabling save/add button once the required field is covered and added email validation too --------- Co-authored-by: LijuJacob08 <liju.jacob08@gmail.com>
1 parent d7b202a commit e329159

29 files changed

Lines changed: 967 additions & 453 deletions

index.html

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,14 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<meta
8-
http-equiv="Content-Security-Policy"
9-
content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https: http://localhost:*; font-src 'self' data:;"
7+
8+
<!-- Inter Font from CDNJS (CSP compliant) -->
9+
<link
10+
rel="stylesheet"
11+
href="https://cdnjs.cloudflare.com/ajax/libs/inter-ui/4.1.1/inter-latin.min.css"
1012
/>
11-
<title>React App Foundation</title>
12-
<script>
13-
(function () {
14-
const savedTheme = localStorage.getItem('theme');
15-
if (
16-
savedTheme === 'dark' ||
17-
(!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches)
18-
) {
19-
document.documentElement.classList.add('dark');
20-
} else {
21-
document.documentElement.classList.remove('dark');
22-
}
23-
})();
24-
</script>
13+
14+
<title>Fluent</title>
2515
</head>
2616

2717
<body>

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@microsoft/applicationinsights-web": "^3.3.8",
3131
"@radix-ui/react-dropdown-menu": "^2.1.14",
3232
"@radix-ui/react-slot": "^1.2.2",
33+
"@tanstack/react-query": "^5.84.1",
3334
"@tanstack/react-router": "^1.120.2",
3435
"@tanstack/react-router-devtools": "^1.120.3",
3536
"class-variance-authority": "^0.7.1",

pnpm-lock.yaml

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

public/locales/en/common.json

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
{
2-
"title": "Scribe Web",
32
"welcome": "Welcome to the Scribe-web",
4-
"sharedCounterExample": "Shared Counter Component Example",
53
"homePage": "Home Page",
6-
"aboutPage": "About Page",
7-
"aboutPageDescription": "This is the about page.",
8-
"sharedComponentExample": "Shared Component & Global State Example",
9-
"globalCount": "Global count from store",
10-
"globalStateCount": "Global State Count",
11-
"localCount": "Local Count",
12-
"reset": "Reset",
13-
"incrementBoth": "Increment Both",
14-
"incrementGobalCount": "Increment Global Count",
15-
"home": "Home",
16-
"about": "About",
17-
"tailwind": "Tailwind Test",
18-
"changeLanguage": "Change Language",
19-
"currentLanguage": "Current language is English",
20-
"counter": "Counter",
21-
"increment": "Increment",
22-
"decrement": "Decrement",
23-
"count": "Count: {{count}}"
4+
"editProfile": "Edit Profile",
5+
"logout": "Logout",
6+
"name": "Name",
7+
"email": "Email",
8+
"role": "Role",
9+
"status": "Status",
10+
"dashboard": "Dashboard",
11+
"users": "Users",
12+
"username": "Display Name",
13+
"firstName": "First Name",
14+
"lastName": "Last Name",
15+
"saveUser": "Save User",
16+
"cancel": "Cancel",
17+
"addUser": "Add User",
18+
"verified": "Verified",
19+
"invited": "Invited"
2420
}

public/locales/hi/common.json

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
{
2-
"title": "स्क्राइब वेब",
32
"welcome": "स्क्राइब-वेब में आपका स्वागत है",
4-
"sharedCounterExample": "शेयर्ड काउंटर कॉम्पोनेंट उदाहरण",
53
"homePage": "होम पेज",
6-
"aboutPage": "अबाउट पेज",
7-
"aboutPageDescription": "यह अबाउट पेज है।",
8-
"sharedComponentExample": "शेयर्ड कॉम्पोनेंट और ग्लोबल स्टेट उदाहरण",
9-
"globalCount": "स्टोर से ग्लोबल काउंट",
10-
"globalStateCount": "ग्लोबल स्टेट काउंट",
11-
"localCount": "लोकल काउंट",
12-
"reset": "रीसेट",
13-
"incrementBoth": "दोनों बढ़ाएँ",
14-
"incrementGobalCount": "ग्लोबल काउंट बढ़ाएँ",
15-
"home": "होम",
16-
"about": "अबाउट",
17-
"tailwind": "टेलविंड परीक्षण",
18-
"changeLanguage": "भाषा बदलें",
19-
"currentLanguage": "वर्तमान भाषा हिंदी है",
20-
"counter": "काउंटर",
21-
"increment": "बढ़ाएँ",
22-
"decrement": "घटाएँ",
23-
"count": "गिनती: {{count}}"
4+
"editProfile": "प्रोफ़ाइल संपादित करें",
5+
"logout": "लॉगआउट",
6+
"name": "नाम",
7+
"email": "ईमेल",
8+
"role": "भूमिका",
9+
"status": "स्थिति",
10+
"dashboard": "डैशबोर्ड",
11+
"users": "उपयोगकर्ता",
12+
"username": "प्रदर्शित नाम",
13+
"firstName": "पहला नाम",
14+
"lastName": "अंतिम नाम",
15+
"saveUser": "उपयोगकर्ता सहेजें",
16+
"cancel": "रद्द करें",
17+
"addUser": "उपयोगकर्ता जोड़ें",
18+
"verified": "सत्यापित",
19+
"invited": "निमंत्रित"
2420
}

src/app.tsx

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useState } from 'react';
22

33
import { Outlet } from '@tanstack/react-router';
44

55
import { ErrorBoundary } from '@/components/ErrorBoundary';
66
import { useAuth } from '@/hooks/useAuth';
7+
import { useGetUserDetailsMutation, useUpdateUser } from '@/hooks/useUsers';
78
import Header from '@/layouts/header';
89
import { Logger } from '@/lib/services/logger';
10+
import { type User } from '@/lib/types';
11+
import { useAppStore } from '@/store/store';
912

1013
export function App() {
11-
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth();
14+
const { user, isAuthenticated, isLoading, loginWithRedirect } = useAuth();
15+
const { mutate: fetchUserDetails, isPending: isFetchingUserDetails } =
16+
useGetUserDetailsMutation();
17+
const updateUserMutation = useUpdateUser();
18+
const { setUserDetail } = useAppStore();
19+
20+
const [userDetailsFetched, setUserDetailsFetched] = useState(false);
21+
const [fetchInitiated, setFetchInitiated] = useState(false);
22+
1223
useEffect(() => {
1324
Logger.logEvent('AppStarted', {
1425
startTime: new Date().toISOString(),
@@ -41,6 +52,7 @@ export function App() {
4152
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
4253
};
4354
}, []);
55+
4456
// Handle authentication redirect
4557
useEffect(() => {
4658
if (!isLoading && !isAuthenticated) {
@@ -56,41 +68,104 @@ export function App() {
5668
returnTo: window.location.pathname + window.location.search,
5769
},
5870
});
71+
} else if (isAuthenticated && user?.email && !fetchInitiated) {
72+
// Log successful authentication
73+
Logger.logEvent('UserAuthenticated', {
74+
userId: user.sub,
75+
userEmail: user.email,
76+
timestamp: new Date().toISOString(),
77+
});
78+
79+
setFetchInitiated(true);
80+
81+
// Fetch user details
82+
fetchUserDetails(user.email, {
83+
onSuccess: userDetails => {
84+
void (async () => {
85+
if (userDetails.status !== 'verified' && user.email_verified) {
86+
userDetails.status = 'verified';
87+
await updateUserMutation.mutateAsync({
88+
userData: userDetails as User,
89+
email: userDetails.email,
90+
});
91+
}
92+
setUserDetail({
93+
id: userDetails.id,
94+
email: userDetails.email,
95+
username: userDetails.username,
96+
role: userDetails.role,
97+
organization: userDetails.organization,
98+
firstName: userDetails.firstName,
99+
lastName: userDetails.lastName,
100+
status: userDetails.status,
101+
});
102+
setUserDetailsFetched(true);
103+
})();
104+
},
105+
onError: error => {
106+
// Log the error
107+
Logger.logException(error instanceof Error ? error : new Error(String(error)), {
108+
source: 'FetchUserDetails',
109+
userEmail: user.email,
110+
});
111+
112+
// Reset states
113+
setFetchInitiated(false);
114+
setUserDetailsFetched(false);
115+
116+
// Redirect to login
117+
void loginWithRedirect({
118+
appState: {
119+
returnTo: window.location.pathname + window.location.search,
120+
},
121+
});
122+
},
123+
});
59124
}
60-
}, [isAuthenticated, isLoading, loginWithRedirect]);
125+
}, [
126+
isAuthenticated,
127+
isLoading,
128+
loginWithRedirect,
129+
user,
130+
fetchUserDetails,
131+
setUserDetail,
132+
fetchInitiated,
133+
updateUserMutation,
134+
]);
61135

62136
if (isLoading) {
63-
return (
64-
<ErrorBoundary>
65-
<div className='flex min-h-screen items-center justify-center bg-gray-50'>
66-
<div className='text-center'>
67-
<div className='mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600'></div>
68-
<p className='text-lg text-gray-600'>Loading...</p>
69-
</div>
70-
</div>
71-
</ErrorBoundary>
72-
);
137+
return LoadingScreen('Loading...');
73138
}
74139

75140
if (!isAuthenticated) {
76-
return (
77-
<ErrorBoundary>
78-
<div className='flex min-h-screen items-center justify-center bg-gray-50'>
79-
<div className='text-center'>
80-
<div className='mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600'></div>
81-
<p className='text-lg text-gray-600'>Redirecting to login...</p>
82-
</div>
83-
</div>
84-
</ErrorBoundary>
85-
);
141+
return LoadingScreen('Redirecting to login...');
142+
}
143+
144+
if (isFetchingUserDetails || !userDetailsFetched) {
145+
return LoadingScreen('Loading user details...');
86146
}
87147

88148
return (
89149
<ErrorBoundary>
90-
<Header />
91-
<main className='p-4'>
92-
<Outlet />
93-
</main>
150+
<div className='flex h-screen flex-col overflow-hidden'>
151+
<Header />
152+
<main className='flex-1 overflow-hidden p-4'>
153+
<Outlet />
154+
</main>
155+
</div>
156+
</ErrorBoundary>
157+
);
158+
}
159+
160+
function LoadingScreen(message: string) {
161+
return (
162+
<ErrorBoundary>
163+
<div className='flex h-screen items-center justify-center bg-gray-50'>
164+
<div className='text-center'>
165+
<div className='mx-auto mb-4 h-12 w-12 animate-spin rounded-full border-b-2 border-blue-600'></div>
166+
<p className='text-lg text-gray-600'>{message}</p>
167+
</div>
168+
</div>
94169
</ErrorBoundary>
95170
);
96171
}

0 commit comments

Comments
 (0)