Skip to content

Commit 1f6ea49

Browse files
committed
Merge branch 'main' into feat/matching/backend-integration
2 parents 4878f40 + fc4f598 commit 1f6ea49

File tree

22 files changed

+317
-38
lines changed

22 files changed

+317
-38
lines changed

.env.sample

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,12 @@ USER_SVC_DB_URI=
1818
JWT_SECRET=
1919
EMAIL_ADDRESS=
2020
EMAIL_PASSWORD=
21+
22+
## Matching service variables
23+
MATCHING_SVC_PORT=6969
24+
25+
## Redis variables
26+
REDIS_PORT=6379
27+
28+
## Redisinsight variables
29+
REDIS_INSIGHT_PORT=5540

docker-compose.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,23 @@ services:
4444
- $MATCHING_SVC_PORT:$MATCHING_SVC_PORT
4545
environment:
4646
- PORT=$MATCHING_SVC_PORT
47+
- REDIS_HOST=redis
48+
- REDIS_PORT=$REDIS_PORT
49+
depends_on:
50+
- redis
4751

4852
redis:
4953
image: redis:7.4-alpine
5054
restart: always
5155
ports:
52-
- 6379:6379
56+
- $REDIS_PORT:$REDIS_PORT
57+
58+
# access RedisInsight at http://localhost:5540
59+
# connect to redis on redis insight at redis:6379
60+
redisinsight:
61+
image: redis/redisinsight:latest
62+
restart: always
63+
ports:
64+
- $REDIS_INSIGHT_PORT:$REDIS_INSIGHT_PORT # Expose RedisInsight UI on port 5540
65+
depends_on:
66+
- redis

frontend/app/app/user-settings/[user_id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function UserSettingsPage({
88
}) {
99
return (
1010
<AuthPageWrapper requireLoggedIn userId={params.user_id}>
11-
<UserSettings userId={params.user_id} />;
11+
<UserSettings userId={params.user_id} />
1212
</AuthPageWrapper>
1313
);
1414
}

frontend/app/auth/auth-context.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@ import {
1212
} from "react";
1313

1414
interface AuthContextType {
15-
user: User | null;
16-
token: string | null;
15+
user: User | null | undefined;
16+
token: string | null | undefined;
1717
login: (email: string, password: string) => Promise<User | undefined>;
1818
logout: () => Promise<void>;
19+
isLoading: boolean;
1920
}
2021

2122
const AuthContext = createContext<AuthContextType | undefined>(undefined);
2223

2324
const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
2425
const tokenKey = "jwtToken";
25-
const [user, setUser] = useState<User | null>(null);
26-
const [token, setToken] = useState<string | null>(null);
26+
const [user, setUser] = useState<User | null>();
27+
const [token, setToken] = useState<string | null>();
28+
const [isLoading, setIsLoading] = useState<boolean>(true);
2729
const router = useRouter();
2830

2931
useEffect(() => {
@@ -32,7 +34,11 @@ const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
3234

3335
// Login using locally stored JWT token
3436
useEffect(() => {
37+
if (token !== undefined) {
38+
setIsLoading(false);
39+
}
3540
if (token) {
41+
setIsLoading(true);
3642
fetch(`${userServiceUri(window.location.hostname)}/auth/verify-token`, {
3743
method: "GET",
3844
headers: {
@@ -42,10 +48,12 @@ const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
4248
.then((res) => {
4349
res.json().then((result) => {
4450
setUser(result.data);
51+
setIsLoading(false);
4552
});
4653
})
4754
.catch((err) => {
4855
console.error(err);
56+
setIsLoading(false);
4957
});
5058
}
5159
}, [token]);
@@ -85,14 +93,15 @@ const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
8593
};
8694

8795
const logout = async () => {
88-
setUser(null);
89-
setToken(null);
90-
localStorage.removeItem("jwtToken");
9196
router.push("/");
97+
localStorage.removeItem("jwtToken");
98+
setUser(undefined);
99+
setToken(undefined);
100+
setIsLoading(true);
92101
};
93102

94103
return (
95-
<AuthContext.Provider value={{ user, token, login, logout }}>
104+
<AuthContext.Provider value={{ user, token, login, logout, isLoading }}>
96105
{children}
97106
</AuthContext.Provider>
98107
);

frontend/app/layout.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import "./globals.css";
44
import { ThemeProvider } from "@/components/theme-provider";
55
import { Toaster } from "@/components/ui/toaster";
66
import AuthProvider from "@/app/auth/auth-context";
7-
import AuthPageWrapper from "@/components/auth/auth-page-wrapper";
87
import { Navbar } from "@/components/navbar";
98

109
const geistSans = localFont({
@@ -41,7 +40,7 @@ export default function RootLayout({
4140
>
4241
<AuthProvider>
4342
<Navbar />
44-
<AuthPageWrapper>{children}</AuthPageWrapper>
43+
{children}
4544
</AuthProvider>
4645
<Toaster />
4746
</ThemeProvider>

frontend/components/admin-user-management/admin-user-management.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from "@/components/ui/table";
1515
import LoadingScreen from "@/components/common/loading-screen";
1616
import AdminEditUserModal from "@/components/admin-user-management/admin-edit-user-modal";
17+
import DeleteAccountModal from "@/components/common/delete-account-modal";
1718
import { PencilIcon, Trash2Icon } from "lucide-react";
1819
import { User, UserArraySchema } from "@/lib/schemas/user-schema";
1920
import { userServiceUri } from "@/lib/api-uri";
@@ -51,25 +52,33 @@ export default function AdminUserManagement() {
5152
const [users, setUsers] = useState<User[]>([]);
5253
const [showModal, setShowModal] = useState<boolean>(false);
5354
const [selectedUser, setSelectedUser] = useState<User>();
55+
const [showDeleteModal, setShowDeleteModal] = useState(false);
56+
const [confirmUsername, setConfirmUsername] = useState("");
57+
const [isDeleteButtonEnabled, setIsDeleteButtonEnabled] = useState(false);
5458

5559
useEffect(() => {
5660
if (data) {
5761
setUsers(data);
5862
}
5963
}, [data]);
6064

65+
// Enable delete button in the delete account modal only when the input username matches the original username
66+
useEffect(() => {
67+
setIsDeleteButtonEnabled(confirmUsername === selectedUser?.username);
68+
}, [confirmUsername, selectedUser]);
69+
6170
if (isLoading) {
6271
return <LoadingScreen />;
6372
}
6473

65-
const handleDelete = async (userId: string) => {
74+
const handleDelete = async () => {
6675
const token = auth?.token;
6776
if (!token) {
6877
throw new Error("No authentication token found");
6978
}
7079

7180
const response = await fetch(
72-
`${userServiceUri(window.location.hostname)}/users/${userId}`,
81+
`${userServiceUri(window.location.hostname)}/users/${selectedUser?.id}`,
7382
{
7483
method: "DELETE",
7584
headers: {
@@ -82,7 +91,7 @@ export default function AdminUserManagement() {
8291
throw new Error("Failed to delete user");
8392
}
8493

85-
setUsers(users.filter((user) => user.id !== userId));
94+
setUsers(users.filter((user) => user.id !== selectedUser?.id));
8695
};
8796

8897
const onUserUpdate = () => {
@@ -100,6 +109,16 @@ export default function AdminUserManagement() {
100109
user={selectedUser}
101110
onUserUpdate={onUserUpdate}
102111
/>
112+
<DeleteAccountModal
113+
showDeleteModal={showDeleteModal}
114+
originalUsername={selectedUser?.username || ""}
115+
confirmUsername={confirmUsername}
116+
setConfirmUsername={setConfirmUsername}
117+
handleDeleteAccount={handleDelete}
118+
isDeleteButtonEnabled={isDeleteButtonEnabled}
119+
setShowDeleteModal={setShowDeleteModal}
120+
isAdmin={true}
121+
/>
103122
<Table>
104123
<TableHeader>
105124
<TableRow>
@@ -130,7 +149,10 @@ export default function AdminUserManagement() {
130149
</Button>
131150
<Button
132151
variant="destructive"
133-
onClick={() => handleDelete(user.id)}
152+
onClick={() => {
153+
setSelectedUser(user);
154+
setShowDeleteModal(true);
155+
}}
134156
>
135157
<Trash2Icon className="h-4 w-4" />
136158
</Button>

frontend/components/auth/auth-page-wrapper.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ReactNode } from "react";
55
import { useAuth } from "@/app/auth/auth-context";
66
import { Button } from "@/components/ui/button";
77
import { useRouter } from "next/navigation";
8+
import LoadingScreen from "../common/loading-screen";
89

910
type AuthCheck = (
1011
user: { id: string; isAdmin: boolean } | undefined | null
@@ -49,7 +50,9 @@ const AuthPageWrapper: React.FC<AuthPageWrapperProps> = ({
4950

5051
return (
5152
<div>
52-
{authCheck(auth?.user) ? (
53+
{auth?.isLoading ? (
54+
<LoadingScreen />
55+
) : authCheck(auth?.user) ? (
5356
children
5457
) : (
5558
<div className="flex items-start justify-center h-2/6">

frontend/components/auth/reset-password-form.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useState } from "react";
44
import { useRouter } from "next/navigation";
55
import { resetPassword } from "@/lib/reset-password";
6+
import { isPasswordComplex } from "@/lib/password";
67
import { useToast } from "@/components/hooks/use-toast";
78

89
import { Button } from "@/components/ui/button";
@@ -24,14 +25,22 @@ export function ResetPasswordForm({ token }: { token: string }) {
2425

2526
const handleSubmit = async (event: React.FormEvent) => {
2627
event.preventDefault();
27-
// TODO: Add validation for password
2828
if (password !== passwordConfirmation) {
2929
toast({
3030
title: "Password Mismatch",
3131
description: "The passwords you entered do not match",
3232
});
3333
return;
3434
}
35+
if (!isPasswordComplex(passwordConfirmation)) {
36+
toast({
37+
title: "Weak Password",
38+
description:
39+
"Password must be at least 8 characters long, include 1 uppercase letter and 1 special character.",
40+
});
41+
return;
42+
}
43+
3544
const res = await resetPassword(token, password);
3645
if (!res.ok) {
3746
toast({

frontend/components/auth/sign-up-form.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useState } from "react";
55
import { useRouter } from "next/navigation";
66
import { toast } from "@/components/hooks/use-toast";
77
import { signUp } from "@/lib/signup";
8+
import { isPasswordComplex } from "@/lib/password";
89

910
import { Button } from "@/components/ui/button";
1011
import {
@@ -34,6 +35,14 @@ export function SignUpForm() {
3435
});
3536
return;
3637
}
38+
if (!isPasswordComplex(passwordConfirmation)) {
39+
toast({
40+
title: "Weak Password",
41+
description:
42+
"Password must be at least 8 characters long, include 1 uppercase letter and 1 special character.",
43+
});
44+
return;
45+
}
3746
const res = await signUp(username, email, password);
3847
if (!res.ok) {
3948
toast({

frontend/components/user-settings/delete-account-modal.tsx renamed to frontend/components/common/delete-account-modal.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface DeleteAccountModalProps {
1717
handleDeleteAccount: () => void;
1818
isDeleteButtonEnabled: boolean;
1919
setShowDeleteModal: (show: boolean) => void;
20+
isAdmin: boolean;
2021
}
2122

2223
const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
@@ -27,6 +28,7 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
2728
handleDeleteAccount,
2829
isDeleteButtonEnabled,
2930
setShowDeleteModal,
31+
isAdmin,
3032
}) => {
3133
return (
3234
<>
@@ -37,10 +39,18 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
3739
<DialogTitle>Confirm Delete Account</DialogTitle>
3840
</DialogHeader>
3941
<div className="space-y-4">
40-
<p>To confirm, please type your username ({originalUsername}):</p>
42+
{isAdmin ? (
43+
<p>
44+
To delete, please confirm the username ({originalUsername}):
45+
</p>
46+
) : (
47+
<p>
48+
To confirm, please type your username ({originalUsername}):
49+
</p>
50+
)}
4151
<Input
4252
type="text"
43-
placeholder="Enter your username"
53+
placeholder="Confirm username"
4454
value={confirmUsername}
4555
onChange={(e) => setConfirmUsername(e.target.value)}
4656
/>
@@ -57,7 +67,11 @@ const DeleteAccountModal: React.FC<DeleteAccountModalProps> = ({
5767
</Button>
5868
<Button
5969
variant="destructive"
60-
onClick={handleDeleteAccount}
70+
onClick={() => {
71+
setShowDeleteModal(false);
72+
setConfirmUsername("");
73+
handleDeleteAccount();
74+
}}
6175
disabled={!isDeleteButtonEnabled}
6276
>
6377
Delete Account

0 commit comments

Comments
 (0)