Skip to content

Commit 15b1e82

Browse files
authored
102 admin approval mid fi (#106)
* feat: UI from figma WIP * responsive UI impl from figma; approved section * feat: handle not found + deny (delete) user * feat: button confirmation popups * refine
1 parent 140ea18 commit 15b1e82

3 files changed

Lines changed: 616 additions & 37 deletions

File tree

apps/backend/src/auth/auth.controller.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,28 @@ export class AuthController {
5959
}
6060
}
6161

62+
@Post('/admin-deny')
63+
@UseGuards(AuthGuard('jwt'))
64+
@UseInterceptors(CurrentUserInterceptor)
65+
async adminDeny(
66+
@Req() req: any,
67+
@Body() body: { email: string },
68+
): Promise<void> {
69+
if (req.user.status !== Status.ADMIN) {
70+
throw new ForbiddenException('Only admins can deny users');
71+
}
72+
try {
73+
await this.authService.deleteUser(body.email);
74+
} catch (e) {
75+
console.error('Admin deny error:', e);
76+
throw new BadRequestException(e.message);
77+
}
78+
const dbUsers = await this.usersService.find(body.email);
79+
if (dbUsers.length > 0) {
80+
await this.usersService.remove(dbUsers[0].id);
81+
}
82+
}
83+
6284
@Get('/users')
6385
@UseGuards(AuthGuard('jwt'))
6486
@UseInterceptors(CurrentUserInterceptor)
@@ -69,9 +91,11 @@ export class AuthController {
6991
const results = await Promise.all(
7092
cognitoUsers.map(async (cu) => {
7193
const email = cu.Attributes.find((a) => a.Name === 'email')?.Value;
94+
const name = cu.Attributes.find((a) => a.Name === 'name')?.Value;
7295
const dbUsers = email ? await this.usersService.find(email) : [];
7396
return {
7497
username: cu.Username,
98+
name,
7599
status: cu.UserStatus, // UNCONFIRMED, CONFIRMED, etc.
76100
email,
77101
dbUser: dbUsers[0] || null,
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import React from 'react';
2+
import { X } from 'lucide-react';
3+
import { Button } from './ui/button';
4+
5+
export interface ConfirmationModalProps {
6+
isOpen: boolean;
7+
onClose: () => void;
8+
onConfirm: () => void;
9+
title: string;
10+
heading: React.ReactNode;
11+
description: React.ReactNode;
12+
confirmText?: string;
13+
cancelText?: string;
14+
confirmVariant?:
15+
| 'default'
16+
| 'outline'
17+
| 'secondary'
18+
| 'ghost'
19+
| 'destructive'
20+
| 'success'
21+
| 'share'
22+
| 'link'
23+
| 'unstyled';
24+
isConfirming?: boolean;
25+
position?: { top?: number; right?: number; bottom?: number; left?: number };
26+
}
27+
28+
export const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
29+
isOpen,
30+
onClose,
31+
onConfirm,
32+
title,
33+
heading,
34+
description,
35+
confirmText = 'Confirm',
36+
cancelText = 'Cancel',
37+
confirmVariant = 'success',
38+
isConfirming = false,
39+
position,
40+
}) => {
41+
if (!isOpen) return null;
42+
43+
const content = (
44+
<div
45+
className={`bg-white rounded-xl ${position ? 'shadow-2xl border border-[#e5e5e5]' : 'shadow-lg'} w-[440px] max-w-full overflow-hidden`}
46+
>
47+
{/* Header */}
48+
<div className="flex items-center justify-between px-6 pt-6 pb-4">
49+
<h2 className="text-xl font-semibold text-[#171717]">{title}</h2>
50+
<button
51+
onClick={onClose}
52+
className="text-gray-500 hover:text-gray-700 transition-colors"
53+
aria-label="Close modal"
54+
>
55+
<X size={20} strokeWidth={2} />
56+
</button>
57+
</div>
58+
59+
{/* Body */}
60+
<div className="px-6 pb-8">
61+
<h3 className="text-lg font-semibold text-[#171717] mb-2">{heading}</h3>
62+
<div className="text-[15px] leading-relaxed text-[#737373]">
63+
{description}
64+
</div>
65+
</div>
66+
67+
{/* Footer */}
68+
<div className="flex items-center gap-4 px-6 pb-6 mt-2">
69+
<Button
70+
variant="outline"
71+
onClick={onClose}
72+
disabled={isConfirming}
73+
className="flex-1 rounded-[10px] h-12 text-base font-normal border-[#e5e5e5] text-black bg-white hover:bg-gray-50 shadow-sm"
74+
>
75+
{cancelText}
76+
</Button>
77+
<Button
78+
variant={confirmVariant}
79+
onClick={onConfirm}
80+
disabled={isConfirming}
81+
className="flex-1 rounded-[10px] h-12 text-base font-normal"
82+
>
83+
{isConfirming ? 'Loading...' : confirmText}
84+
</Button>
85+
</div>
86+
</div>
87+
);
88+
89+
if (position) {
90+
return (
91+
<>
92+
{/* Invisible backdrop to capture outside clicks and close the popover */}
93+
<div className="fixed inset-0 z-40" onClick={onClose} />
94+
<div
95+
className="fixed z-50 font-['Source_Sans_Pro']"
96+
style={{ ...position, position: 'fixed' }}
97+
>
98+
{content}
99+
</div>
100+
</>
101+
);
102+
}
103+
104+
return (
105+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm font-['Source_Sans_Pro']">
106+
{content}
107+
</div>
108+
);
109+
};

0 commit comments

Comments
 (0)