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
81 changes: 81 additions & 0 deletions frontend/app/(auth)/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
'use client';

import { useState } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { api } from '@/lib/api';

const schema = z.object({
password: z.string().min(8, 'Password must be at least 8 characters'),
confirmPassword: z.string().min(8, 'Please confirm your password'),
}).refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});

type FormValues = z.infer<typeof schema>;

export default function ResetPasswordPage() {
const searchParams = useSearchParams();
const router = useRouter();
const token = searchParams.get('token') || '';
const [success, setSuccess] = useState(false);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);

const { register, handleSubmit, formState: { errors } } = useForm<FormValues>({ resolver: zodResolver(schema) });

const onSubmit = async (values: FormValues) => {
setError('');
setLoading(true);
try {
await api.post('/auth/reset-password', { token, password: values.password });
setSuccess(true);
setTimeout(() => router.push('/login'), 3000);
} catch (err: any) {
const msg = err?.response?.data?.message;
setError(msg || 'Invalid or expired token. Please request a new reset link.');
} finally {
setLoading(false);
}
};

if (!token) {
return (
<>
<h2 className="text-xl font-semibold text-gray-900 mb-1">Reset Password</h2>
<p className="text-sm text-red-500 bg-red-50 border border-red-200 rounded-lg px-3 py-3 mt-4">
No reset token found. Please request a new password reset link.
</p>
</>
);
}

if (success) {
return (
<>
<h2 className="text-xl font-semibold text-gray-900 mb-1">Password Reset</h2>
<div className="bg-green-50 border border-green-200 rounded-lg px-3 py-4 text-sm text-green-700 mt-4">
Your password has been reset successfully. Redirecting to login...
</div>
</>
);
}

return (
<>
<h2 className="text-xl font-semibold text-gray-900 mb-1">Reset your password</h2>
<p className="text-sm text-gray-500 mb-6">Enter a new password for your account.</p>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Input id="password" label="New password" type="password" placeholder="********" autoComplete="new-password" {...register('password')} error={errors.password?.message} />
<Input id="confirmPassword" label="Confirm new password" type="password" placeholder="********" autoComplete="new-password" {...register('confirmPassword')} error={errors.confirmPassword?.message} />
{error && <p className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg px-3 py-2">{error}</p>}
<Button type="submit" size="lg" loading={loading} className="w-full mt-2">Reset password</Button>
</form>
</>
);
}
40 changes: 40 additions & 0 deletions frontend/components/assets/on-chain-tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import { useState } from "react";
import { Wallet } from "lucide-react";
import { useWalletStore } from "@/store/wallet.store";
import { TokenizationPanel } from "@/components/tokenization/tokenization-panel";
import { GovernancePanel } from "@/components/governance/governance-panel";
import { DividendsPanel } from "@/components/dividends/dividends-panel";

type Tab = "tokenization" | "governance" | "dividends";

export function OnChainTab({ assetId }: { assetId: string }) {
const [tab, setTab] = useState<Tab>("tokenization");
const { isConnected } = useWalletStore();

if (!isConnected) {
return (
<div className="bg-white border rounded-xl p-12 text-center">
<Wallet className="w-12 h-12 text-gray-300 mx-auto mb-3" />
<p className="text-gray-500 text-sm">Connect a Freighter wallet to view on-chain features.</p>
</div>
);
}

return (
<div>
<div className="flex gap-2 mb-6">
{(["tokenization", "governance", "dividends"] as const).map((t) => (
<button key={t} onClick={() => setTab(t)}
className={`px-4 py-2 rounded-lg text-sm font-medium ${tab === t ? "bg-gray-900 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`}>
{t.charAt(0).toUpperCase() + t.slice(1)}
</button>
))}
</div>
{tab === "tokenization" && <TokenizationPanel assetId={assetId} />}
{tab === "governance" && <GovernancePanel assetId={assetId} />}
{tab === "dividends" && <DividendsPanel assetId={assetId} />}
</div>
);
}
28 changes: 28 additions & 0 deletions frontend/hooks/useAssetQr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import { useState, useEffect } from 'react';
import { api } from '@/lib/api';

export function useAssetQr(assetId: string) {
const [qrUrl, setQrUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);

useEffect(() => {
let cancelled = false;
setLoading(true);
api
.get(`/assets/${assetId}/qr`, { responseType: 'blob' })
.then((r) => {
if (cancelled) return;
const url = URL.createObjectURL(r.data);
setQrUrl(url);
})
.catch(() => { if (!cancelled) setError(true); })
.finally(() => { if (!cancelled) setLoading(false); });

return () => { cancelled = true; };
}, [assetId]);

return { qrUrl, loading, error };
}
6 changes: 1 addition & 5 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@
"axios": "^1.13.2",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"jspdf": "^4.0.0",
"lucide-react": "^0.562.0",
"next": "15.5.9",
"qrcode": "^1.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-dropzone": "^14.3.8",
Expand All @@ -45,11 +43,9 @@
"@testing-library/react": "^16.3.2",
"@types/jest": "^30.0.0",
"@types/node": "^20",
"@types/qrcode": "^1.5.6",
"@types/react": "^19",
"@playwright/test": "^1.49.1",
"@types/react-dom": "^19",
"@types/recharts": "^1.8.29",
"autoprefixer": "^10.4.23",
"eslint": "^9",
"eslint-config-next": "15.5.4",
Expand All @@ -60,4 +56,4 @@
"ts-jest": "^29.4.4",
"typescript": "^5"
}
}
}
Loading