diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..527c421 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,11 @@ +# Public app URL used for canonical links and callback generation. +NEXT_PUBLIC_APP_URL=http://localhost:3000 + +# Backend API that powers payment intent + claim verification requests. +NEXT_PUBLIC_API_BASE_URL=http://localhost:4000 + +# Chain/network selector used by the reference claim UI. +NEXT_PUBLIC_CRYPTO_NETWORK=stellar-testnet + +# Human support destination shown on error fallback states. +NEXT_PUBLIC_SUPPORT_EMAIL=support@example.com diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..0f8c21c --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,52 @@ +# Bridgelet Frontend + +Reference Next.js UI for initiating and claiming crypto payments. + +## Tech stack + +- Next.js 16 (App Router) +- TypeScript 5 in strict mode +- Tailwind CSS 4 + +## Local setup + +1. Install dependencies: + + ```bash + cd frontend + npm install + ``` + +2. Configure environment variables: + + ```bash + cp .env.example .env.local + ``` + +3. Start development server: + + ```bash + npm run dev + ``` + +4. Open [http://localhost:3000](http://localhost:3000). + +## Routes + +- `/` homepage placeholder +- `/send` sender flow placeholder +- `/claim/[token]` recipient claim placeholder + +## Quality checks + +- Type-check only: + + ```bash + npm run typecheck + ``` + +- Production build: + + ```bash + npm run build + ``` diff --git a/frontend/app/claim/[token]/page.tsx b/frontend/app/claim/[token]/page.tsx new file mode 100644 index 0000000..fd85f96 --- /dev/null +++ b/frontend/app/claim/[token]/page.tsx @@ -0,0 +1,28 @@ +import { PageShell } from '@/components/page-shell'; +import { publicEnv } from '@/lib/env'; + +type ClaimPageProps = { + params: Promise<{ token: string }>; +}; + +export default async function ClaimPage({ params }: ClaimPageProps) { + const { token } = await params; + + return ( + +
+
+
Claim Token
+
{token}
+
+
+
Support Contact
+
{publicEnv.NEXT_PUBLIC_SUPPORT_EMAIL}
+
+
+
+ ); +} diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..c37a02e --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,9 @@ +@import "tailwindcss"; + +:root { + color-scheme: light; +} + +body { + @apply min-h-screen bg-slate-50 text-slate-900 antialiased; +} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..b454340 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next'; +import type { ReactNode } from 'react'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Bridgelet Payments', + description: 'Reference UI for sending and claiming crypto payments.' +}; + +type RootLayoutProps = { + children: ReactNode; +}; + +export default function RootLayout({ children }: RootLayoutProps) { + return ( + + {children} + + ); +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..769f0c3 --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,29 @@ +import Link from 'next/link'; +import { PageShell } from '@/components/page-shell'; + +export default function HomePage() { + return ( + +
+

Select a flow to continue.

+ +
+
+ ); +} diff --git a/frontend/app/send/page.tsx b/frontend/app/send/page.tsx new file mode 100644 index 0000000..8be47d4 --- /dev/null +++ b/frontend/app/send/page.tsx @@ -0,0 +1,22 @@ +import { PageShell } from '@/components/page-shell'; +import { publicEnv } from '@/lib/env'; + +export default function SendPage() { + return ( + +
+
+
API Base URL
+
{publicEnv.NEXT_PUBLIC_API_BASE_URL}
+
+
+
Network
+
{publicEnv.NEXT_PUBLIC_CRYPTO_NETWORK}
+
+
+
+ ); +} diff --git a/frontend/components/page-shell.tsx b/frontend/components/page-shell.tsx new file mode 100644 index 0000000..d473a4c --- /dev/null +++ b/frontend/components/page-shell.tsx @@ -0,0 +1,19 @@ +import type { ReactNode } from 'react'; + +type PageShellProps = { + title: string; + description: string; + children?: ReactNode; +}; + +export function PageShell({ title, description, children }: PageShellProps) { + return ( +
+
+

{title}

+

{description}

+
+
{children}
+
+ ); +} diff --git a/frontend/lib/env.ts b/frontend/lib/env.ts new file mode 100644 index 0000000..0505446 --- /dev/null +++ b/frontend/lib/env.ts @@ -0,0 +1,21 @@ +type PublicEnv = { + NEXT_PUBLIC_APP_URL: string; + NEXT_PUBLIC_API_BASE_URL: string; + NEXT_PUBLIC_CRYPTO_NETWORK: string; + NEXT_PUBLIC_SUPPORT_EMAIL: string; +}; + +function readPublicEnv(name: keyof PublicEnv): string { + const value = process.env[name]; + if (!value || value.trim().length === 0) { + return 'not-configured'; + } + return value; +} + +export const publicEnv: PublicEnv = { + NEXT_PUBLIC_APP_URL: readPublicEnv('NEXT_PUBLIC_APP_URL'), + NEXT_PUBLIC_API_BASE_URL: readPublicEnv('NEXT_PUBLIC_API_BASE_URL'), + NEXT_PUBLIC_CRYPTO_NETWORK: readPublicEnv('NEXT_PUBLIC_CRYPTO_NETWORK'), + NEXT_PUBLIC_SUPPORT_EMAIL: readPublicEnv('NEXT_PUBLIC_SUPPORT_EMAIL') +}; diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..52eb120 --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,4 @@ +/// +/// + +// NOTE: This file should not be edited. diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..91ef62f --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +module.exports = nextConfig; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2d76cfd --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "bridgelet-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "next": "^16.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.0.0", + "@types/node": "^22.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=20.10.0" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..6ebbee3 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + '@tailwindcss/postcss': {} + } +}; + +export default config; diff --git a/frontend/public/.gitkeep b/frontend/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..c748abb --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": false, + "skipLibCheck": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + }, + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "types": ["node"], + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}