Skip to content

Commit 2f3486d

Browse files
committed
chore: pen ultimate
1 parent 056dac7 commit 2f3486d

File tree

20 files changed

+1041
-360
lines changed

20 files changed

+1041
-360
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,5 @@ workbox-*.js.map
5757
sw.js
5858
sw.js.*
5959

60-
out
60+
out
61+
.env.local

bun.lockb

417 Bytes
Binary file not shown.

next.config.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ const config = {
6363
{ source: "/api/healthz", destination: "/api/health" },
6464
{ source: "/health", destination: "/api/health" },
6565
{ source: "/ping", destination: "/api/health" },
66+
{
67+
source: '/api/:path*',
68+
destination: 'https://hackmit-2024-server.vercel.app/api/:path*',
69+
},
6670
]
6771
},
6872
async headers() {

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"@biomejs/biome": "1.9.3",
4040
"@clerk/clerk-react": "^5.11.0",
4141
"@clerk/nextjs": "^5.7.1",
42+
"@clerk/themes": "^2.1.35",
4243
"@ducanh2912/next-pwa": "^10.2.9",
4344
"@hookform/resolvers": "^3.9.0",
4445
"@million/lint": "^1.0.0",

src/app/(auth)/sign-in/[[...sign-in]]/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { SignIn } from "@clerk/nextjs";
22
import React from "react";
33

44
export default function Page() {
5-
return <SignIn />;
5+
return <SignIn routing="path" path="/sign-in" />;
66
}

src/app/(auth)/sign-up/[[...sign-up]]/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { SignUp } from "@clerk/nextjs";
22
import React from "react";
33

44
export default function Page() {
5-
return <SignUp />;
5+
return <SignUp routing="path" path="/sign-up" />;
66
}

src/app/(main)/demo/page.tsx

+308
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
"use client";
2+
3+
import { useState, useEffect } from 'react';
4+
import { api } from '@/app/api/v1/api';
5+
import { Button } from '@/components/ui/button';
6+
import { Input } from '@/components/ui/input';
7+
import { Label } from '@/components/ui/label';
8+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
9+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
10+
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
11+
import { Loader2, AlertCircle, CheckCircle } from 'lucide-react';
12+
import { motion, AnimatePresence } from 'framer-motion';
13+
import { useUser } from "@clerk/nextjs";
14+
15+
type TransactionResult = {
16+
id: string;
17+
amount: number;
18+
productCategory: string;
19+
customerLocation: string;
20+
accountAgeDays: number;
21+
transactionDate: string;
22+
fraudProbability: number;
23+
};
24+
25+
const productCategories = [
26+
'Electronics',
27+
'Clothing',
28+
'Home & Garden',
29+
'Sports & Outdoors',
30+
'Books',
31+
'Toys & Games',
32+
'Health & Beauty',
33+
'Automotive',
34+
'Jewelry',
35+
'Food & Grocery',
36+
];
37+
38+
const locations = [
39+
{ value: "new-york", label: "New York", country: "United States" },
40+
{ value: "london", label: "London", country: "United Kingdom" },
41+
{ value: "paris", label: "Paris", country: "France" },
42+
{ value: "tokyo", label: "Tokyo", country: "Japan" },
43+
{ value: "sydney", label: "Sydney", country: "Australia" },
44+
{ value: "berlin", label: "Berlin", country: "Germany" },
45+
{ value: "mumbai", label: "Mumbai", country: "India" },
46+
{ value: "rio-de-janeiro", label: "Rio de Janeiro", country: "Brazil" },
47+
{ value: "cape-town", label: "Cape Town", country: "South Africa" },
48+
{ value: "toronto", label: "Toronto", country: "Canada" },
49+
];
50+
51+
export default function DemoPage() {
52+
const { user, isLoaded: isUserLoaded } = useUser();
53+
const [userId, setUserId] = useState<string | null>(null);
54+
const [formData, setFormData] = useState({
55+
amount: '',
56+
productCategory: '',
57+
customerLocation: '',
58+
accountAgeDays: '',
59+
transactionDate: '',
60+
});
61+
const [result, setResult] = useState<TransactionResult | null>(null);
62+
const [error, setError] = useState<string | null>(null);
63+
const [isLoading, setIsLoading] = useState(false);
64+
const [isRegistering, setIsRegistering] = useState(false);
65+
66+
useEffect(() => {
67+
const now = new Date();
68+
const offset = now.getTimezoneOffset();
69+
const localDate = new Date(now.getTime() - (offset * 60 * 1000));
70+
setFormData(prev => ({
71+
...prev,
72+
transactionDate: localDate.toISOString().slice(0, 16)
73+
}));
74+
}, []);
75+
76+
useEffect(() => {
77+
async function registerUser() {
78+
if (isUserLoaded && user) {
79+
setIsRegistering(true);
80+
try {
81+
const existingUser = await api.getUser(user.id);
82+
setUserId(existingUser.id);
83+
} catch (error) {
84+
console.error('Error getting user:', error);
85+
try {
86+
const newUser = await api.createUser({
87+
id: user.id,
88+
name: user.fullName || 'Anonymous',
89+
email: user.primaryEmailAddress?.emailAddress || '[email protected]',
90+
});
91+
setUserId(newUser.id);
92+
} catch (createError) {
93+
console.error('Error creating user:', createError);
94+
setError('Failed to register user. Please try again later or contact support.');
95+
}
96+
} finally {
97+
setIsRegistering(false);
98+
}
99+
}
100+
}
101+
102+
registerUser();
103+
}, [isUserLoaded, user]);
104+
105+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
106+
const { id, value } = e.target;
107+
setFormData(prev => ({ ...prev, [id]: value }));
108+
};
109+
110+
const handleSubmit = async (e: React.FormEvent) => {
111+
e.preventDefault();
112+
if (!userId) {
113+
setError('User not registered. Please try again.');
114+
return;
115+
}
116+
117+
setIsLoading(true);
118+
setError(null);
119+
setResult(null);
120+
121+
try {
122+
const transaction = await api.createTransaction({
123+
userId,
124+
amount: parseFloat(formData.amount),
125+
productCategory: formData.productCategory,
126+
customerLocation: formData.customerLocation,
127+
accountAgeDays: parseInt(formData.accountAgeDays),
128+
transactionDate: new Date(formData.transactionDate).toISOString(),
129+
});
130+
setResult(transaction as TransactionResult);
131+
} catch (error) {
132+
console.error('Error creating transaction:', error);
133+
setError('Failed to create transaction. Please try again.');
134+
} finally {
135+
setIsLoading(false);
136+
}
137+
};
138+
139+
if (!isUserLoaded || isRegistering) {
140+
return (
141+
<div className="flex items-center justify-center h-screen">
142+
<Loader2 className="h-8 w-8 animate-spin text-primary" />
143+
<span className="ml-2 text-lg">Loading...</span>
144+
</div>
145+
);
146+
}
147+
148+
return (
149+
<div className="container mx-auto p-4 max-w-2xl">
150+
<Card>
151+
<CardHeader>
152+
<CardTitle>Transaction Demo</CardTitle>
153+
<CardDescription>Test our fraud detection system with a sample transaction</CardDescription>
154+
</CardHeader>
155+
<CardContent>
156+
<form onSubmit={handleSubmit} className="space-y-4">
157+
<div className="space-y-2">
158+
<Label htmlFor="amount">Amount ($)</Label>
159+
<Input
160+
id="amount"
161+
type="number"
162+
value={formData.amount}
163+
onChange={handleInputChange}
164+
placeholder="Enter transaction amount"
165+
required
166+
min="0.01"
167+
step="0.01"
168+
/>
169+
</div>
170+
<div className="space-y-2">
171+
<Label htmlFor="productCategory">Product Category</Label>
172+
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, productCategory: value }))} value={formData.productCategory}>
173+
<SelectTrigger>
174+
<SelectValue placeholder="Select a category" />
175+
</SelectTrigger>
176+
<SelectContent>
177+
{productCategories.map((category) => (
178+
<SelectItem key={category} value={category}>{category}</SelectItem>
179+
))}
180+
</SelectContent>
181+
</Select>
182+
</div>
183+
<div className="space-y-2">
184+
<Label htmlFor="customerLocation">Customer Location</Label>
185+
<Select onValueChange={(value) => setFormData(prev => ({ ...prev, customerLocation: value }))} value={formData.customerLocation}>
186+
<SelectTrigger>
187+
<SelectValue placeholder="Select location" />
188+
</SelectTrigger>
189+
<SelectContent>
190+
{locations.map((location) => (
191+
<SelectItem key={location.value} value={location.value}>
192+
{location.label}, {location.country}
193+
</SelectItem>
194+
))}
195+
</SelectContent>
196+
</Select>
197+
</div>
198+
<div className="space-y-2">
199+
<Label htmlFor="accountAgeDays">Account Age (Days)</Label>
200+
<Input
201+
id="accountAgeDays"
202+
type="number"
203+
value={formData.accountAgeDays}
204+
onChange={handleInputChange}
205+
placeholder="Enter account age in days"
206+
required
207+
min="0"
208+
/>
209+
</div>
210+
<div className="space-y-2">
211+
<Label htmlFor="transactionDate">Transaction Date</Label>
212+
<Input
213+
id="transactionDate"
214+
type="datetime-local"
215+
value={formData.transactionDate}
216+
onChange={handleInputChange}
217+
required
218+
/>
219+
</div>
220+
</form>
221+
</CardContent>
222+
<CardFooter>
223+
<Button type="submit" onClick={handleSubmit} disabled={isLoading} className="w-full">
224+
{isLoading ? (
225+
<>
226+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
227+
Processing...
228+
</>
229+
) : (
230+
'Create Transaction'
231+
)}
232+
</Button>
233+
</CardFooter>
234+
</Card>
235+
236+
<AnimatePresence>
237+
{error && (
238+
<motion.div
239+
initial={{ opacity: 0, y: 20 }}
240+
animate={{ opacity: 1, y: 0 }}
241+
exit={{ opacity: 0, y: -20 }}
242+
transition={{ duration: 0.3 }}
243+
className="mt-4"
244+
>
245+
<Alert variant="destructive">
246+
<AlertCircle className="h-4 w-4" />
247+
<AlertTitle>Error</AlertTitle>
248+
<AlertDescription>{error}</AlertDescription>
249+
</Alert>
250+
</motion.div>
251+
)}
252+
253+
{result && (
254+
<motion.div
255+
initial={{ opacity: 0, y: 20 }}
256+
animate={{ opacity: 1, y: 0 }}
257+
exit={{ opacity: 0, y: -20 }}
258+
transition={{ duration: 0.3 }}
259+
className="mt-4"
260+
>
261+
<Card>
262+
<CardHeader>
263+
<CardTitle className="flex items-center">
264+
<CheckCircle className="mr-2 h-5 w-5 text-green-500" />
265+
Transaction Result
266+
</CardTitle>
267+
</CardHeader>
268+
<CardContent>
269+
<dl className="space-y-2">
270+
<div className="flex justify-between">
271+
<dt className="font-semibold">Transaction ID:</dt>
272+
<dd>{result.id}</dd>
273+
</div>
274+
<div className="flex justify-between">
275+
<dt className="font-semibold">Amount:</dt>
276+
<dd>${result.amount.toFixed(2)}</dd>
277+
</div>
278+
<div className="flex justify-between">
279+
<dt className="font-semibold">Product Category:</dt>
280+
<dd>{result.productCategory}</dd>
281+
</div>
282+
<div className="flex justify-between">
283+
<dt className="font-semibold">Customer Location:</dt>
284+
<dd>{result.customerLocation}</dd>
285+
</div>
286+
<div className="flex justify-between">
287+
<dt className="font-semibold">Account Age:</dt>
288+
<dd>{result.accountAgeDays} days</dd>
289+
</div>
290+
<div className="flex justify-between">
291+
<dt className="font-semibold">Transaction Date:</dt>
292+
<dd>{new Date(result.transactionDate).toLocaleString()}</dd>
293+
</div>
294+
<div className="flex justify-between">
295+
<dt className="font-semibold">Fraud Probability:</dt>
296+
<dd className={result.fraudProbability > 0.5 ? 'text-red-500' : 'text-green-500'}>
297+
{(result.fraudProbability * 100).toFixed(2)}%
298+
</dd>
299+
</div>
300+
</dl>
301+
</CardContent>
302+
</Card>
303+
</motion.div>
304+
)}
305+
</AnimatePresence>
306+
</div>
307+
);
308+
}

src/app/(main)/layout.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Navigation from '@/components/Navigation'
2+
import React from 'react'
3+
4+
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
5+
return (
6+
<>
7+
<Navigation />
8+
<main className="flex-1 overflow-y-auto p-4">
9+
{children}
10+
</main>
11+
</>
12+
)
13+
}
14+
15+
export default DashboardLayout
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"use client"
2+
3+
import { UserProfile } from "@clerk/nextjs"
4+
5+
export default function Settings() {
6+
7+
return (
8+
<div className="container mx-auto px-4 py-8">
9+
<h1 className="text-3xl font-bold mb-6 text-primary">Account Settings</h1>
10+
<div className="bg-card rounded-lg shadow-lg overflow-hidden">
11+
<UserProfile />
12+
</div>
13+
</div>
14+
)
15+
}

src/app/(main)/settings/page.tsx

-11
This file was deleted.

0 commit comments

Comments
 (0)