diff --git a/e2e/auth-flow.spec.ts b/e2e/auth-flow.spec.ts
index 6687775..71ed246 100644
--- a/e2e/auth-flow.spec.ts
+++ b/e2e/auth-flow.spec.ts
@@ -2,12 +2,16 @@ import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test('should handle login, registration, and logout', async ({ page }) => {
- // Visit site → home page loads → see navbar
+ // Visit site → redirects to /home → see bottom nav
await page.goto('/');
await expect(page.locator('nav')).toBeVisible();
+ // Click Profile tab to find Login button
+ await page.click('text=Profile');
+ await expect(page).toHaveURL(/\/profile/);
+
// Click Login → see login form
- await page.click('text=Login');
+ await page.click('a[href="/login"]');
await expect(page.locator('form')).toBeVisible();
// Try invalid login → see error message
@@ -16,23 +20,27 @@ test.describe('Authentication Flow', () => {
await page.click('button[type="submit"]');
await expect(page.locator('text=Invalid credentials')).toBeVisible();
- // Navigate to registration (assuming a link exists on login page)
- await page.click('text=Register');
+ // Navigate to registration
+ await page.click('a[href="/register"]');
// Fill registration form → submit → redirect to home
- await page.fill('input[name="name"]', 'Test User');
- await page.fill('input[name="email"]', 'newuser@example.com');
- await page.fill('input[name="password"]', 'securepassword123');
+ const randomEmail = `newuser_${Date.now()}_${Math.floor(Math.random() * 1000)}@example.com`;
+ await page.fill('input[type="text"]', 'Test User');
+ await page.fill('input[type="email"]', randomEmail);
+ await page.fill('input[placeholder="Min. 8 characters"]', 'securepassword123');
await page.click('button[type="submit"]');
// Check redirect and authentication state
- await expect(page).toHaveURL('/');
- // See user menu or similar in navbar when authenticated
- await expect(page.locator('nav')).toContainText('Logout');
+ await expect(page).toHaveURL(/\/nur/);
- // Click logout → redirect to home → see login button
- await page.click('text=Logout');
- await expect(page).toHaveURL('/');
- await expect(page.locator('text=Login')).toBeVisible();
+ // Go to Profile tab and check authenticated state
+ await page.click('text=Profile');
+ await expect(page.locator('nav')).toContainText('Profile');
+ await expect(page.locator('button:has-text("Logout")')).toBeVisible();
+
+ // Click logout → redirect to login → see login button
+ await page.click('button:has-text("Logout")');
+ await expect(page).toHaveURL(/\/login/);
+ await expect(page.locator('button:has-text("Sign In")')).toBeVisible();
});
});
diff --git a/next-env.d.ts b/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/playwright.config.ts b/playwright.config.ts
index d61dbbc..74c77c9 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -19,7 +19,7 @@ export default defineConfig({
},
],
webServer: {
- command: 'npm run dev',
+ command: 'node .next/standalone/server.js',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
diff --git a/src/app/api/seed/route.ts b/src/app/api/seed/route.ts
index c30b9fc..8bd1bea 100644
--- a/src/app/api/seed/route.ts
+++ b/src/app/api/seed/route.ts
@@ -7,10 +7,10 @@ export async function POST() {
const password = await bcrypt.hash('password123', 10)
const userData = [
- { email: 'ghazali@falahos.my', name: 'Abu Hamid Al-Ghazali', password, flhBalance: 5000 },
- { email: 'ibnabbas@falahos.my', name: 'Abdullah Ibn Abbas', password, flhBalance: 3000 },
- { email: 'rabia@falahos.my', name: "Rabi'a Al-Adawiyya", password, flhBalance: 2000 },
- { email: 'demo@falahos.my', name: 'Demo User', password, flhBalance: 500 },
+ { email: 'ghazali@falahos.my', name: 'Abu Hamid Al-Ghazali', passwordHash: password, flhBalance: 5000 },
+ { email: 'ibnabbas@falahos.my', name: 'Abdullah Ibn Abbas', passwordHash: password, flhBalance: 3000 },
+ { email: 'rabia@falahos.my', name: "Rabi'a Al-Adawiyya", passwordHash: password, flhBalance: 2000 },
+ { email: 'demo@falahos.my', name: 'Demo User', passwordHash: password, flhBalance: 500 },
]
const users: any[] = []
diff --git a/src/app/api/waqf/contribute/route.ts b/src/app/api/waqf/contribute/route.ts
new file mode 100644
index 0000000..e183387
--- /dev/null
+++ b/src/app/api/waqf/contribute/route.ts
@@ -0,0 +1,46 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { prisma } from '@/lib/prisma'
+import { verifyJWT } from '@/lib/auth'
+
+export async function POST(req: NextRequest) {
+ try {
+ const auth = req.headers.get('authorization')
+ if (!auth?.startsWith('Bearer ')) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+ }
+
+ const payload = await verifyJWT(auth.slice(7))
+ if (!payload) {
+ return NextResponse.json({ error: 'Invalid token' }, { status: 401 })
+ }
+
+ const { projectId, amountFlh } = await req.json()
+ if (!projectId || !amountFlh || amountFlh <= 0) {
+ return NextResponse.json({ error: 'Invalid project or amount' }, { status: 400 })
+ }
+
+ const user = await prisma.user.findUnique({ where: { id: payload.id } })
+ if (!user) {
+ return NextResponse.json({ error: 'User not found' }, { status: 404 })
+ }
+
+ if (user.flhBalance < amountFlh) {
+ return NextResponse.json({ error: 'Insufficient FLH balance' }, { status: 400 })
+ }
+
+ // Decrement the user's balance
+ const updatedUser = await prisma.user.update({
+ where: { id: payload.id },
+ data: { flhBalance: { decrement: amountFlh } },
+ })
+
+ return NextResponse.json({
+ success: true,
+ newBalance: updatedUser.flhBalance,
+ message: `Successfully contributed ${amountFlh} FLH to the project.`
+ })
+ } catch (e) {
+ console.error(e)
+ return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
+ }
+}
diff --git a/src/app/dhikr/page.tsx b/src/app/dhikr/page.tsx
index 6201a8f..a0a76f7 100644
--- a/src/app/dhikr/page.tsx
+++ b/src/app/dhikr/page.tsx
@@ -109,14 +109,14 @@ export default function DhikrPage() {
if (!user) {
return (
-
+
📿
-
Daily Dhikr
-
+
Daily Dhikr
+
Build a daily habit of remembrance. Sign in to track your streak.
@@ -125,24 +125,24 @@ export default function DhikrPage() {
if (loading) {
return (
-
-
+
)
}
return (
-
+
{/* Header gradient */}
-
+
Daily Dhikr
-
100 remembrances · after prayer
+
100 remembrances · after prayer
{/* Streak badge inside header */}
-
@@ -157,22 +157,22 @@ export default function DhikrPage() {
key={i}
className={`flex-1 py-1.5 rounded-xl text-center transition-all duration-300 ${
isActive
- ? 'bg-white shadow-sm border border-[#D4AF37]/40'
+ ? 'bg-bg-card shadow-lg border border-gold/40'
: isDone
- ? 'bg-white/60 border border-gray-100'
- : 'bg-white/40 border border-gray-100'
+ ? 'bg-bg-card/60 border border-border/60'
+ : 'bg-bg-card/30 border border-border/30'
}`}
>
{d.transliteration}
@@ -183,18 +183,18 @@ export default function DhikrPage() {
{/* Completed today (not just-finished) */}
{todayCompleted && !justFinished ? (
-
-
-
+
+
+
-
MashaAllah!
-
You've completed your daily dhikr
-
-
-
{streak} day streak
+
MashaAllah!
+
You've completed your daily dhikr
+
+
+ {streak} day streak
-
@@ -202,20 +202,20 @@ export default function DhikrPage() {
) : justFinished ? (
/* Completion screen */
-
✨
-
SubhanAllah!
-
100 remembrances complete
-
+
✨
+
SubhanAllah!
+
100 remembrances complete
+
- {streak} day streak
+ {streak} day streak
-
-
+
+
“Whoever says SubhanAllah 33 times, Alhamdulillah 33 times, and Allahu Akbar 34 times after each prayer…”
-
— Sahih Muslim
+
— Sahih Muslim
-
+
Repeat again
@@ -223,11 +223,11 @@ export default function DhikrPage() {
) : (
/* Main counter */
- {/* Ring counter — white card */}
-
+ {/* Ring counter — card */}
+
@@ -244,7 +244,7 @@ export default function DhikrPage() {
{/* SVG progress ring */}
{/* Dhikr text below ring, inside card */}
-
{current.arabic}
+
{current.arabic}
{current.transliteration}
-
{current.meaning}
+
{current.meaning}
-
Tap to count
+
Tap to count
)}
{/* Bottom progress bar */}
{!justFinished && (
-
-
+
+
Total today
{Math.min(totalCount, TOTAL)} / {TOTAL}
-
+
{ setToast(msg); setTimeout(() => setToast(''), 3000) }
return (
-
+
{/* Header */}
-
+
{view === 'threads' && (
-
{ setView('categories'); setSelectedCat(null) }} className="w-9 h-9 flex items-center justify-center rounded-xl bg-gray-100 active:opacity-60">
-
+ { setView('categories'); setSelectedCat(null) }} className="w-9 h-9 flex items-center justify-center rounded-xl bg-bg-card border border-border active:opacity-60 cursor-pointer">
+
)}
-
{view === 'threads' && selectedCat ? selectedCat.name : 'Forum'}
-
+
{view === 'threads' && selectedCat ? selectedCat.name : 'Forum'}
+
{view === 'categories' ? 'Community discussions' : `${threads.length} threads`}
@@ -59,7 +59,7 @@ export default function ForumPage() {
{token && (
setShowCreate(true)}
- className="flex items-center gap-1.5 bg-emerald-600 text-white px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.97] transition-transform"
+ className="flex items-center gap-1.5 gold-gradient text-bg-deep px-4 py-2.5 rounded-xl font-bold text-sm active:scale-[0.97] transition-transform cursor-pointer shadow-sm shadow-gold/10"
>
Post
@@ -70,7 +70,7 @@ export default function ForumPage() {
{loading && (
{[...Array(4)].map((_, i) => (
-
+
))}
)}
@@ -81,23 +81,23 @@ export default function ForumPage() {
{categories.length === 0 ? (
🕌
-
No categories yet
+
No categories yet
) : categories.map(c => (
{ setSelectedCat(c); setView('threads'); loadThreads(c.id) }}
- className="w-full text-left bg-white rounded-2xl shadow-sm border border-gray-100 p-4 active:scale-[0.97] transition-transform"
+ className="w-full text-left bg-bg-card rounded-2xl border border-border p-4 active:scale-[0.97] transition-transform hover:border-gold/30 cursor-pointer"
>
-
+
{c.icon || '💬'}
-
{c.name}
-
{c.description}
+
{c.name}
+
{c.description}
-
+
))}
@@ -110,21 +110,21 @@ export default function ForumPage() {
{threads.length === 0 ? (
💬
-
No threads yet
-
Start the first discussion
+
No threads yet
+
Start the first discussion
) : threads.map(t => (
-
-
{t.title}
-
{t.content}
+
+
{t.title}
+
{t.content}
-
-
{t.author.name[0]}
+
+ {t.author.name[0]}
-
{t.author.name}
+
{t.author.name}
-
+
{t._count.posts}
@@ -136,7 +136,7 @@ export default function ForumPage() {
{/* Toast */}
{toast && (
-
+
{toast}
)}
@@ -182,38 +182,38 @@ function CreateThreadSheet({ token, categories, onClose, onCreated, showToast }:
return (