Create a table nfts with columns:
- id: uuid (default uuid_generate_v4()) primary key
- name: text not null
- description: text
- media_url: text not null
- media_type: text not null # e.g., image/png, video/mp4
- price_eth: numeric
- chain: text not null # e.g., ethereum, polygon, sepolia
- owner_email: text
- owner_username: text
- created_at: timestamp with time zone default now()
And a public storage bucket if you plan to upload files via Supabase Storage (not wired yet here).
Full-stack Next.js app with Supabase auth (custom email verification via OTP), profile management, and password reset flow. Animated UI built with motion.
- Next.js App Router (src/app)
- Supabase Auth (SSR cookies via
@supabase/ssr) - Signup with username + email/password
- OTP verification emailed via Resend
- OTP expires after 10 minutes
- Login with email/password
- Blocks unverified users
- Custom message if email doesn’t exist
- Forgot password
- Sends Supabase reset link
- Non-enumerable responses (always generic success)
- Reset page updates password using recovery session
- Minimal user menu on Home page showing username and email
- Type-safe validation with Zod and React Hook Form
src/lib/supabase/server.ts: SSR Supabase client with cookie managementadmin.ts: service-role client for server actionsbrowser.ts: browser client for client-side flows (reset password)
src/app/api/auth/signup/route.ts: creates user, writes profile, generates OTP, sends emailverify-otp/route.ts: checks OTP + expiry, verifies auth emaillogin/route.ts: signs in; if fail, checks if email exists, returns tailored errorsforgot-password/route.ts: sends reset link and returns generic success
src/app/(auth)/signup/: signup UIlogin/: login UIverify-email/: OTP input UI (expectsuidandemailquery)forgot-password/: request reset UIreset-password/: set new password after email link
src/app/Home/page.tsx: sample home with user icon popover
Create a .env.local with:
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=... # server-only
NEXT_PUBLIC_SITE_URL=http://localhost:3000 # for building reset links
# Email provider for OTP (Resend)
RESEND_API_KEY=...
EMAIL_FROM="Enefty <[email protected]>"
Database tables (Supabase):
profiles(RLS as needed)id: uuid(PK, references auth.users.id)username: text(unique)is_verified: booleanotp_code: text(sha256 hex)otp_expires_at: timestamptz
Install dependencies and run dev server:
npm install
npm run dev- Signup
- POST
/api/auth/signupwith email/password/username - Creates user, saves profile, emails OTP
- UI redirects to
/verify-email?uid=...&email=...
- Verify Email
- POST
/api/auth/verify-otpwithuid+otp - Verifies if OTP matches and not expired; sets
is_verified
- Login
- POST
/api/auth/login - If bad credentials and email not found: returns "You don't have an account. Sign up"
- If unverified: blocks login
- Forgot/Reset Password
- POST
/api/auth/forgot-passwordwith email (always returns generic success) - User clicks email link to
/reset-password?type=recovery... - Page updates password via
supabase.auth.updateUser({ password })
- OTP is 4 digits; stored as SHA-256 hash with 10-minute expiry.
- The forgot-password endpoint avoids account enumeration by returning generic success.
- Ensure
NEXT_PUBLIC_SITE_URLis set correctly in production for password reset links.
npm run dev– start dev with Turbopacknpm run build– buildnpm run start– start production servernpm run lint– run eslint
MIT