Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
ci:
name: Lint, Type-check & Build
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Type check
run: npx tsc --noEmit

- name: Build
run: npm run build
4 changes: 2 additions & 2 deletions app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export default function ForgotPasswordPage() {
`/reset-password?email=${encodeURIComponent(email)}`,
);
}, 2000);
} catch (err: any) {
setError(err.message || "Something went wrong");
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Something went wrong');
} finally {
setIsLoading(false);
}
Expand Down
11 changes: 4 additions & 7 deletions app/(auth)/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
import Image from 'next/image';
import Link from 'next/link';
import { login } from '@/lib/api/auth';
import { useAuthStore } from '@/hooks/use-auth-store';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

export default function SignInPage() {
const router = useRouter();
const [tempEmail, setTempEmail] = useState<string | null>(null);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
Expand All @@ -28,14 +26,13 @@ export default function SignInPage() {
setIsLoading(true);

try {
const res = await login({ email, password });
await login({ email, password });
setIsLoading(false);
setTempEmail(email); // store for OTP step
sessionStorage.setItem('login-email', email); // persist for OTP page
sessionStorage.setItem('login-email', email);
router.push('/verify-otp');
} catch (err: any) {
} catch (err: unknown) {
setIsLoading(false);
setError(err.message || 'Login failed');
setError(err instanceof Error ? err.message : 'Login failed');
}
};

Expand Down
21 changes: 9 additions & 12 deletions app/(auth)/verify-otp/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import { useRouter } from 'next/navigation';
export default function VerifyOtpPage() {
const router = useRouter();
const setAuth = useAuthStore((s) => s.setAuth);
const [email, setEmail] = useState<string | null>(null);
useEffect(() => {
setEmail(sessionStorage.getItem('login-email'));
}, []);
const [otp, setOtp] = useState(['', '', '', '', '', '']);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
Expand Down Expand Up @@ -68,35 +64,36 @@ export default function VerifyOtpPage() {
setError('Please enter all 6 digits');
return;
}
if (!email) {
const storedEmail = sessionStorage.getItem('login-email');
if (!storedEmail) {
setError('No email found. Please sign in again.');
return;
}
setIsLoading(true);
setError('');
try {
const res = (await verifyLoginOtp({ email, otp: otpCode })) as any;
// { accessToken, refreshToken, expiresIn, user }
const res = await verifyLoginOtp({ email: storedEmail, otp: otpCode }) as { user: { id: string; name: string; email: string; role: 'USER' | 'ADMIN' }; accessToken: string; refreshToken: string };
setAuth(res.user, res.accessToken, res.refreshToken);
setIsLoading(false);
router.push('/dashboard');
} catch (err: any) {
} catch (err: unknown) {
setIsLoading(false);
setError(err.message || 'Invalid or expired OTP');
setError(err instanceof Error ? err.message : 'Invalid or expired OTP');
}
};

const handleResend = async () => {
setOtp(['', '', '', '', '', '']);
setError('');
inputRefs.current[0]?.focus();
if (!email) {
const storedEmail = sessionStorage.getItem('login-email');
if (!storedEmail) {
setError('Missing email. Please sign in again.');
return;
}
try {
await resendLoginOtp({ email });
} catch (err: any) {
await resendLoginOtp({ email: storedEmail });
} catch {
setError('Failed to resend code');
}
};
Expand Down
68 changes: 34 additions & 34 deletions app/(dashboard)/transactions/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import { useState, useEffect, useRef } from "react";
import { Transaction, getTransactions } from "../../lib/api/transactions";
import { TransactionFilters } from "../../components/transactions/transaction-filters";
import { TransactionTable } from "../../components/transactions/transaction-table";
import { TransactionList } from "../../components/transactions/transaction-list";
import { TransactionPagination } from "../../components/transactions/pagination";
import { TransactionEmptyState } from "../../components/transactions/empty-state";
import { TransactionDetails } from "../../components/transactions/transaction-details";
import { exportTransactionsToCSV, generateCSVFilename } from "../../lib/utils/csv-export";
import { Transaction, getTransactions } from "@/lib/api/transactions";
import { TransactionFilters } from "@/components/transactions/transaction-filters";
import { TransactionTable } from "@/components/transactions/transaction-table";
import { TransactionList } from "@/components/transactions/transaction-list";
import { TransactionPagination } from "@/components/transactions/pagination";
import { TransactionEmptyState } from "@/components/transactions/empty-state";
import { TransactionDetails } from "@/components/transactions/transaction-details";
import { exportTransactionsToCSV, generateCSVFilename } from "@/app/lib/utils/csv-export";

const ITEMS_PER_PAGE = 10;

Expand Down Expand Up @@ -66,42 +66,42 @@ export default function TransactionsPage() {
}
};

useEffect(() => {
useEffect(() => {
let cancelled = false;
setIsLoading(true);
setError(null);

const typeParam =
activeFilter === "Withdrawal"
? "Withdraw"
: activeFilter !== "All"
? activeFilter
: undefined;

getTransactions({
page: currentPage,
limit: ITEMS_PER_PAGE,
search: debouncedSearch || undefined,
type: typeParam,
from: dateFrom || undefined,
to: dateTo || undefined,
})
.then((result) => {

const fetchTransactions = async () => {
const typeParam =
activeFilter === 'Withdrawal'
? 'Withdraw'
: activeFilter !== 'All'
? activeFilter
: undefined;

try {
const result = await getTransactions({
page: currentPage,
limit: ITEMS_PER_PAGE,
search: debouncedSearch || undefined,
type: typeParam,
from: dateFrom || undefined,
to: dateTo || undefined,
});
if (!cancelled) {
setTransactions(result.data);
setTotalItems(result.total);
}
})
.catch((err) => {
} catch (err) {
if (!cancelled) {
setError(
err instanceof Error ? err.message : "Failed to load transactions"
err instanceof Error ? err.message : 'Failed to load transactions'
);
}
})
.finally(() => {
} finally {
if (!cancelled) setIsLoading(false);
});
}
};

fetchTransactions();

return () => {
cancelled = true;
Expand Down
4 changes: 2 additions & 2 deletions app/api/exchange-rates/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export async function GET(req: NextRequest) {

const data = await externalRes.json();
return NextResponse.json(data);
} catch (error: any) {
} catch (error: unknown) {
return NextResponse.json(
{ error: error.message || "Failed to fetch exchange rates" },
{ error: error instanceof Error ? error.message : 'Failed to fetch exchange rates' },
{ status: 500 }
);
}
Expand Down
17 changes: 0 additions & 17 deletions app/components/transactions/empty-state.tsx

This file was deleted.

93 changes: 0 additions & 93 deletions app/components/transactions/pagination.tsx

This file was deleted.

Loading
Loading