Project Overview
GOALS: The Definition of Determination — book website + admin dashboard for Sharif Dyson. The static frontend is complete. This guide covers everything the dev team needs to build the production backend.
Recommended Tech Stack
Production stack for the GOALS book backend and admin dashboard.
4-Week Sprint Plan
Recommended development timeline from repo setup to production launch.
| Week | Focus | Deliverables | Owner |
|---|---|---|---|
| Week 1 | Infrastructure & Deploy | S3 bucket + static site sync, CloudFront distribution, Route 53 for sharifdyson.com, ACM SSL certificate, goalsthebook.com redirect (Lightsail or S3) | DevOps |
| Week 2 | Backend Foundation | Next.js project init, RDS PostgreSQL setup, Prisma schema + migrations, NextAuth config, base API routes (orders, customers, subscribers, contacts) | Backend |
| Week 3 | Dashboard UI & Integrations | Next.js dashboard pages (matching admin.html prototype), Square payment webhook, AWS SES email integration, CSV export, broadcast email module | Full-Stack |
| Week 4 | Testing & Launch | Square sandbox → production, end-to-end order flow testing, SSL verification, DNS propagation, staging → production cutover, monitoring setup | QA + DevOps |
AWS Architecture
Full cloud infrastructure map for the GOALS book website and admin backend.
┌─ Users ──────────────────────────────────────────────────────┐ │ sharifdyson.com → Route 53 → CloudFront → S3 Bucket │ │ goalsbook.com → Route 53 → S3 Redirect → /order │ └──────────────────────────────────────────────────────────────┘ ┌─ Admin Dashboard ────────────────────────────────────────────┐ │ admin.sharifdyson.com → Vercel (Next.js) │ │ → AWS RDS PostgreSQL (VPC) │ │ → Square API (payments) │ │ → AWS SES (emails) │ └──────────────────────────────────────────────────────────────┘ S3 Bucket: sharifdyson-goals-book (us-east-1, static website) CloudFront: PriceClass_100 · HTTPS · custom domain · ACM cert RDS: PostgreSQL 15 · db.t3.micro · Multi-AZ optional SES: us-east-1 · Production mode · [email protected]
Create S3 Bucket
Bucket name: sharifdyson-goals-book. Region: us-east-1. Enable static website hosting. Set index document to index.html and error document to index.html. Disable "Block all public access".
Sync Files to S3
Run: aws s3 sync . s3://sharifdyson-goals-book --exclude "*.md" --delete
Create CloudFront Distribution
Origin: S3 website endpoint. Alternate domain: sharifdyson.com, www.sharifdyson.com. Attach ACM certificate. Default root object: index.html. Enable HTTPS redirect.
Route 53 DNS for sharifdyson.com
Transfer from Network Solutions: update NS records to Route 53 name servers. Create A-alias record → CloudFront distribution. Create CNAME for www → CloudFront domain.
goalsthebook.com Redirect
You already own goalsthebook.com on AWS Lightsail. Create an S3 redirect bucket named goalsthebook.com. Configure static website hosting to redirect all requests to https://sharifdyson.com. Point Lightsail DNS A record → S3 bucket endpoint, or migrate DNS to Route 53 for easier management.
ACM SSL Certificate
Request certificate in us-east-1 for: sharifdyson.com, www.sharifdyson.com, goalsthebook.com, www.goalsthebook.com. Validate via DNS (Route 53 or Lightsail auto-creates validation records). Attach to CloudFront.
Next.js Dashboard Setup
Bootstrap the production admin dashboard with Next.js 14 App Router.
# Create Next.js app npx create-next-app@latest goals-dashboard \ --typescript --tailwind --app --src-dir --eslint cd goals-dashboard # Install dependencies npm install \ @prisma/client prisma \ next-auth @auth/prisma-adapter \ @square/web-sdk square \ @aws-sdk/client-ses \ react-hook-form zod @hookform/resolvers \ recharts @tanstack/react-table \ axios date-fns \ @radix-ui/react-dialog @radix-ui/react-select
goals-dashboard/ ├── src/ │ ├── app/ │ │ ├── dashboard/ │ │ │ ├── page.tsx # Overview │ │ │ ├── orders/ │ │ │ │ ├── page.tsx # Orders table │ │ │ │ └── [id]/page.tsx # Order detail │ │ │ ├── customers/page.tsx │ │ │ ├── subscribers/page.tsx │ │ │ ├── broadcast/page.tsx │ │ │ └── settings/page.tsx │ │ ├── api/ │ │ │ ├── auth/[...nextauth]/route.ts │ │ │ ├── orders/route.ts │ │ │ ├── orders/[id]/route.ts │ │ │ ├── customers/route.ts │ │ │ ├── subscribers/route.ts │ │ │ ├── contacts/route.ts │ │ │ ├── broadcast/route.ts │ │ │ └── webhooks/square/route.ts │ │ └── auth/ │ │ └── signin/page.tsx │ ├── components/ │ │ ├── ui/ │ │ ├── dashboard/ │ │ └── charts/ │ └── lib/ │ ├── prisma.ts │ ├── auth.ts │ ├── square.ts │ └── ses.ts ├── prisma/ │ └── schema.prisma └── .env.local
Database Schema
PostgreSQL schema via Prisma. Mirrors the Table API data structure used in the admin prototype.
// prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Order { id String @id @default(cuid()) name String email String phone String? format String // hardcover | paperback | digital amount Decimal @db.Decimal(10, 2) status String @default("pending") source String @default("website") notes String? squareId String? @unique // Square payment ID createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([email]) @@index([createdAt]) } model Customer { id String @id @default(cuid()) firstName String lastName String email String @unique phone String? city String? state String? format String? source String @default("manual") notes String? lists String[] // goals | book2 | ecosystem | ooki createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([email]) } model Subscriber { id String @id @default(cuid()) email String @unique name String? lists String[] source String @default("website") createdAt DateTime @default(now()) @@index([email]) } model Contact { id String @id @default(cuid()) name String email String type String @default("general") message String status String @default("new") createdAt DateTime @default(now()) @@index([createdAt]) } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model User { id String @id @default(cuid()) name String? email String? @unique password String? role String @default("admin") accounts Account[] sessions Session[] createdAt DateTime @default(now()) }
# Create and apply migration npx prisma migrate dev --name init # Generate Prisma client npx prisma generate # Seed admin user npx prisma db seed
API Routes
All Next.js API routes needed for the admin dashboard functionality.
import { NextRequest, NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; import { prisma } from '@/lib/prisma'; import { z } from 'zod'; // Zod validation schema const OrderSchema = z.object({ name: z.string().min(1), email: z.string().email(), phone: z.string().optional(), format: z.enum(['hardcover', 'paperback', 'digital']), amount: z.number().positive(), status: z.enum(['pending', 'processing', 'completed', 'refunded']).default('pending'), source: z.string().optional(), notes: z.string().optional(), }); // GET — list orders export async function GET(req: NextRequest) { const session = await getServerSession(authOptions); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const { searchParams } = new URL(req.url); const page = parseInt(searchParams.get('page') || '1'); const limit = parseInt(searchParams.get('limit') || '20'); const search = searchParams.get('search') || ''; const [orders, total] = await prisma.$transaction([ prisma.order.findMany({ where: search ? { OR: [{ name: { contains: search, mode: 'insensitive' } }, { email: { contains: search, mode: 'insensitive' } }] } : {}, orderBy: { createdAt: 'desc' }, skip: (page - 1) * limit, take: limit }), prisma.order.count() ]); return NextResponse.json({ data: orders, total, page, limit }); } // POST — create order export async function POST(req: NextRequest) { const session = await getServerSession(authOptions); if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); const body = await req.json(); const parsed = OrderSchema.safeParse(body); if (!parsed.success) return NextResponse.json({ error: parsed.error }, { status: 400 }); const order = await prisma.order.create({ data: parsed.data }); return NextResponse.json(order, { status: 201 }); }
Auth — NextAuth.js
Secure admin login with NextAuth and credentials provider (email + password).
import { NextAuthOptions } from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; import { PrismaAdapter } from '@auth/prisma-adapter'; import { prisma } from './prisma'; import bcrypt from 'bcryptjs'; export const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma), session: { strategy: 'jwt' }, pages: { signIn: '/auth/signin' }, providers: [ CredentialsProvider({ name: 'Credentials', credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' } }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) return null; const user = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!user?.password) return null; const valid = await bcrypt.compare(credentials.password, user.password); return valid ? user : null; } }) ], callbacks: { async jwt({ token, user }) { if (user) token.role = user.role; return token; }, async session({ session, token }) { session.user.role = token.role; return session; } } };
Square Payments
Square SDK integration for book order checkout and webhook for auto-creating order records.
4111 1111 1111 1111import { NextRequest, NextResponse } from 'next/server'; import { Client, Environment } from 'square'; import { prisma } from '@/lib/prisma'; import { sendOrderConfirmation } from '@/lib/ses'; import crypto from 'crypto'; export async function POST(req: NextRequest) { const body = await req.text(); const signature = req.headers.get('x-square-hmacsha256-signature'); // Verify Square signature const sigKey = process.env.SQUARE_WEBHOOK_SIGNATURE_KEY!; const url = `https://admin.sharifdyson.com/api/webhooks/square`; const hash = crypto.createHmac('sha256', sigKey).update(url + body).digest('base64'); if (hash !== signature) return NextResponse.json({ error: 'Invalid' }, { status: 401 }); const event = JSON.parse(body); // Handle successful payment if (event.type === 'payment.completed') { const payment = event.data.object.payment; const note = payment.note || ''; // format stored in note const order = await prisma.order.create({ data: { name: payment.buyerEmailAddress || 'Unknown', email: payment.buyerEmailAddress || '', format: note.includes('hardcover') ? 'hardcover' : note.includes('paperback') ? 'paperback' : 'digital', amount: payment.totalMoney.amount / 100, status: 'completed', source: 'website', squareId: payment.id } }); await sendOrderConfirmation(order); } return NextResponse.json({ received: true }); }
AWS SES Email
Transactional emails (order confirmation) and broadcast emails to subscriber lists.
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'; const ses = new SESClient({ region: 'us-east-1' }); export async function sendOrderConfirmation(order: any) { await ses.send(new SendEmailCommand({ Source: '[email protected]', Destination: { ToAddresses: [order.email] }, Message: { Subject: { Data: `Your GOALS Book Order — ${order.format}` }, Body: { Html: { Data: ` <h2>Thank you for your order, ${order.name}!</h2> <p>You ordered: <strong>GOALS: The Definition of Determination</strong> (${order.format})</p> <p>Amount: $${order.amount}</p> <p>— Sharif Dyson | sharifdyson.com</p> ` } } } })); } export async function sendBroadcast(emails: string[], subject: string, htmlBody: string) { // SES has 50 recipients per send — chunk them const chunks = []; for (let i = 0; i < emails.length; i += 50) chunks.push(emails.slice(i, i + 50)); for (const chunk of chunks) { await ses.send(new SendEmailCommand({ Source: '[email protected]', Destination: { BccAddresses: chunk }, Message: { Subject: { Data: subject }, Body: { Html: { Data: htmlBody } } } })); } }
Dashboard Page Map
Next.js pages to build for the production dashboard. The admin.html prototype shows the exact UI for each.
/dashboard
Overview: revenue cards, monthly chart, format doughnut, recent orders table, recent subscribers. Matches admin.html overview view.
/dashboard/orders
All orders table with search, filter by format/status, pagination, CSV export. Detail modal on row click.
/dashboard/customers
Customer directory with search, mailing list badges, format badge. Add customer form on side panel.
/dashboard/subscribers
Combined customers + standalone subscribers. Filter tabs by list (goals, book2, ecosystem, ooki). CSV export.
/dashboard/broadcast
Email composer with list selector, subject, message body, quick templates. Calls /api/broadcast which uses SES.
/dashboard/contacts
Contact form inbox. Mark as read/replied/archived. Delete. Export.
/dashboard/settings
Password change (bcrypt), site links, database record counts, Square connection status.
Environment Variables
All required env vars for the Next.js admin dashboard. Set these in Vercel dashboard or .env.local.
# Database (AWS RDS PostgreSQL) DATABASE_URL="postgresql://goals_user:[email protected]:5432/goals_db" # NextAuth NEXTAUTH_SECRET="generate-with-openssl-rand-base64-32" NEXTAUTH_URL="https://admin.sharifdyson.com" # Square Payments SQUARE_ACCESS_TOKEN="sq0atp-YOUR_PRODUCTION_TOKEN" SQUARE_LOCATION_ID="YOUR_SQUARE_LOCATION_ID" SQUARE_WEBHOOK_SIGNATURE_KEY="YOUR_WEBHOOK_SIGNATURE_KEY" NEXT_PUBLIC_SQUARE_APP_ID="sq0idp-YOUR_APP_ID" NEXT_PUBLIC_SQUARE_LOCATION_ID="YOUR_SQUARE_LOCATION_ID" # AWS SES (email) AWS_REGION="us-east-1" AWS_ACCESS_KEY_ID="AKIA..." AWS_SECRET_ACCESS_KEY="..." FROM_EMAIL="[email protected]" REPLY_TO_EMAIL="[email protected]" # App NEXT_PUBLIC_SITE_URL="https://sharifdyson.com" NEXT_PUBLIC_ADMIN_URL="https://admin.sharifdyson.com"
Go-Live Checklist
Complete every item before going live to production.
sharifdyson.com/admin.html (password: Goals2026!SD) shows the exact UI the Next.js dashboard should replicate. All data schemas are documented above. Static site → see the AWS deployment guide for step-by-step S3/CloudFront instructions.