diff --git a/README.md b/README.md index c0835eb..bf04586 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,84 @@ # AgentPage -Public profile builder for real estate agents. Agents get a branded page at `/:username` with listings, reviews, lead capture, and widgets. (Linktree alternative) +Public profile builder for real estate agents. Each agent gets a branded page at `/:username` with listings, reviews, lead capture, and widgets — a Linktree alternative built for real estate. -## Tech Stack - -| Layer | Technology | -| -------------- | ------------------------------------------------------- | -| Framework | Next.js 15 App Router | -| Language | TypeScript | -| Database | PostgreSQL + Prisma 7 (`prisma db push`, no migrations) | -| Auth | Lucia v3 sessions + Google OAuth (Arctic) | -| Payments | Stripe — plans `solo` / `pro` | -| Storage | AWS S3 + CloudFront | -| Rate Limiting | AWS DynamoDB | -| Email | Resend | -| i18n | next-intl (`en`, `fr`) | -| UI dashboard | Tailwind CSS 4 + DaisyUI | -| UI public page | Custom theme system — no DaisyUI | - -## Environment Variables - -```env -APP_URL= # e.g. https://yourdomain.com -DATABASE_URL= -AWS_REGION= -AWS_ACCESS_KEY_ID= -AWS_SECRET_ACCESS_KEY= -BUCKET_NAME= # S3 bucket for uploads -CLOUDFRONT_URL= # Private (server-side) CDN URL -NEXT_PUBLIC_CLOUDFRONT_URL= # Public CDN URL for tags -RATE_LIMITS_DYNAMODB_TABLE= -RESEND_API_KEY= -SENDER_EMAIL= -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= -STRIPE_SECRET_KEY= -STRIPE_WEBHOOK_SECRET= -STRIPE_PRICE_SOLO_MONTHLY= # Stripe Price ID -STRIPE_PRICE_SOLO_ANNUAL= -STRIPE_PRICE_PRO_MONTHLY= -STRIPE_PRICE_PRO_ANNUAL= -``` - -## Setup +## Getting Started ```bash -bun install && cp .env.example .env.local # fill in env vars -bunx prisma db push # sync schema to DB +bun install +bunx prisma db push bun dev ``` -## Project Structure - -``` -src/ - app/ - [locale]/ - (dashboard)/ # Authenticated dashboard routes - (public)/[username]/ # Public agent page - api/ - auth/ # register, login, logout, google oauth - dashboard/ # profile, links, reviews, listings, widgets, themes - stripe/ # checkout, portal, webhook - lib/ - auth.ts # getUser() → { user, session } | { user: null } - db.ts # Prisma client singleton - env.ts # Type-safe env via @t3-oss/env-nextjs - subscription.ts # isPro(), getActivePlan(), hasActiveSubscription() - themes.ts # getTheme() → ThemeConfig - components/ - ui/pro-gate.tsx # Locks UI behind pro plan - generated/prisma/ # Prisma client output -locales/ - en.json # All translation keys - fr.json -``` - -## Key Patterns - -### Auth - -`getUser()` from `@/lib/auth` returns `{ user, session }` or `{ user: null, session: null }`. Use in server components and API routes. Sessions stored in `Session` table; cookies set via Lucia. - -### Subscriptions & Pro Gating - -Plans: `solo` (paid base) and `pro` (paid advanced). Status values: `active`, `trialing`, `past_due`, `canceled`. - -```ts -import { isPro } from "@/lib/subscription"; -const pro = await isPro(userId); // true if active/trialing pro subscription -``` - -Use `` wrapper for client components. Stripe customer ID is created eagerly at registration so the billing portal is always accessible. - -### Theme System (public page only) - -Dashboard uses DaisyUI. The public `/:username` page uses a custom theme — **never use DaisyUI classes here**. - -```ts -import { getTheme } from "@/lib/themes"; -const theme = getTheme( - user.theme, - user.accentColor, - user.buttonRadius, - user.fontFamily, - user.fontSize -); -// theme.bg, theme.cardBg, theme.cardText, theme.border, theme.muted → Tailwind classes -// theme.accentHex, theme.accentTextColor → hex strings (use inline style) -// theme.buttonRadius → Tailwind class (e.g. "rounded-lg") -// theme.fontFamily, theme.fontSize → CSS string / Tailwind class -``` - -### i18n - -- Server components: `const t = await getTranslations("namespace")` -- Client components: `const t = useTranslations("namespace")` -- All keys live in `locales/en.json` and `locales/fr.json` -- Namespaces: `common`, `pages.dashboard`, `pages.dashboard-listings`, `pages.agent-page`, etc. - -### Image Uploads - -Upload via `POST /api/dashboard/images` → returns CloudFront URL. Use `NEXT_PUBLIC_CLOUDFRONT_URL` in `` src. - -### Stripe Webhook - -Events at `POST /api/stripe/webhook`. On `checkout.session.completed`: cancels existing active/trialing subscriptions before creating the new one. Syncs status on `updated`, `deleted`, and `invoice.payment_*`. - -## Database Models - -`User` · `Session` · `Token` · `Subscription` · `Link` · `Review` · `Analytics` · `Lead` · `Listing` +## Tech Stack -Schema managed with `prisma db push` (no migrations). Client output in `src/generated/prisma`. +| | | +| ------------- | ------------------------------------------- | +| **Framework** | Next.js 16 · React 19 · TypeScript 5.9 | +| **Database** | PostgreSQL · Prisma 7 | +| **Auth** | Lucia v3 · Google OAuth (Arctic) | +| **Payments** | Stripe 19 | +| **Storage** | AWS S3 · CloudFront | +| **Email** | Resend | +| **UI** | Tailwind CSS 4 · DaisyUI 5 | +| **i18n** | next-intl 4 (EN / FR) | +| **Infra** | Pulumi (IaC) · AWS DynamoDB (rate limiting) | + +## Features + +**Public agent page** + +- Customizable profile — photo, bio, brokerage, license, specialties +- Links with click tracking and icons +- Testimonials with star ratings +- Property listings with photo, price, beds/baths +- Widgets: mortgage calculator, home valuation, Calendly booking +- Contact / lead capture form +- Theme system: 8 base themes, 14 accent colors, font and radius controls + +**Dashboard** + +- Analytics: 7-day page views, link clicks, lead submissions +- Lead inbox + CRM integrations (Zapier, Follow Up Boss, Mailchimp) +- Billing portal: plan switching, invoice history +- Multi-session management with browser/OS tracking +- Live preview of public page + +## Subscriptions + +| Plan | Price | +| -------- | ---------------------- | +| **Solo** | $19/mo · $15/mo annual | +| **Pro** | $39/mo · $31/mo annual | + +Pro unlocks: listings, widgets (mortgage calculator, home valuation, Calendly), and CRM integrations (Zapier, Follow Up Boss, Mailchimp). New accounts get a 14-day trial. + +Stripe handles checkout, billing portal, and webhooks. On new checkout, prior active subscriptions are automatically cancelled. Agents receive email notifications on payment failures and cancellation. + +## SEO + +- Per-agent dynamic OG metadata (name, bio, avatar) +- Schema.org `RealEstateAgent` JSON-LD with `AggregateRating` +- ISR on public pages (revalidate every 3600s) +- Environment-aware `robots.txt` (blocks crawlers on dev/preview) +- Vercel Analytics + Speed Insights + +## Technical Highlights + +- **Auth** — Lucia sessions (12-week expiry) with IP/browser/OS tracking. bcrypt for passwords. Google OAuth via Arctic. +- **Rate limiting** — DynamoDB sliding-window rate limiter with per-endpoint limits (e.g. login: 5 req/3min, contact: 3 req/hr). Graceful degradation on DynamoDB outage. +- **Image pipeline** — Presigned S3 uploads (5MB max, JPEG/PNG/WebP), served via CloudFront CDN. Old files deleted on replacement. +- **CRM integrations** — Lead submissions fan out asynchronously post-response to Zapier, Follow Up Boss, and Mailchimp. Non-blocking — does not affect response time. +- **Infrastructure as code** — AWS resources (S3, CloudFront, DynamoDB) provisioned with Pulumi. Separate dev/prod stacks. +- **Theme system** — Fully composable: base theme + accent color + button radius + font family/size. Applied via Tailwind classes and inline hex values; dashboard (DaisyUI) and public page use separate styling layers. + +## Possible Improvements + +- Queue/retry mechanism for CRM webhook delivery (currently fire-and-forget) +- Activate remaining i18n locales (FR, DE, ES, PT, IT — config exists, routing disabled) +- Analytics event batching/sampling at scale +- Email format validation before forwarding leads to Follow Up Boss +- Complete Google Reviews auto-pull (referenced in upgrade modal, not yet implemented) ## License