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.

Static frontend is done. All HTML pages (index, book, author, contact, order, admin, download, devhandoff) are complete and match the GOALS app aesthetic. The admin dashboard runs on the Table API for prototype data persistence. Dev team picks up here to build the production backend.
What's Built (Static)
index.html — Home page
book.html — Book detail & pricing
author.html — Sharif Dyson bio
contact.html — Contact form
order.html — Checkout page
admin.html — Dashboard (Supabase connected)
download.html — Dev downloads center
css/style.css — Global styles
Dev Team Builds
Next.js admin dashboard app
Supabase PostgreSQL — live (4 tables, free tier)
Lambda functions — Supabase-connected, ready to deploy
NextAuth secure admin login
Square payment webhook
AWS SES broadcast emails
Static site → S3 + CloudFront
Route 53 DNS — sharifdyson.com + goalsthebook.com live

Recommended Tech Stack

Production stack for the GOALS book backend and admin dashboard.

Frontend
Static site — S3 + CloudFront (existing HTML)
Next.js 14 — Admin dashboard (App Router)
Tailwind CSS — Dashboard styling
Recharts — Revenue charts
TanStack Table — Data tables
Backend
Next.js API Routes — REST endpoints
Prisma ORM — Database access
PostgreSQL — AWS RDS (us-east-1)
NextAuth.js — Admin authentication
Zod — Input validation
Infrastructure
AWS S3 — Static file hosting
CloudFront — CDN + HTTPS
Route 53 — DNS management
AWS SES — Transactional email
Vercel — Next.js hosting

4-Week Sprint Plan

Recommended development timeline from repo setup to production launch.

WeekFocusDeliverablesOwner
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.

AWS Architecture Map
  ┌─ 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]
        
1

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".

2

Sync Files to S3

Run: aws s3 sync . s3://sharifdyson-goals-book --exclude "*.md" --delete

3

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.

4

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.

5

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.

6

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.

Terminal — Bootstrap
# 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
Folder Structure
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
// 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())
}
Terminal — Run Migrations
# 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.

MethodRouteDescription
GET/api/ordersList all orders with pagination, search, sort
POST/api/ordersCreate new order (manual or Square webhook)
GET/api/orders/[id]Get single order detail
PUT/api/orders/[id]Update order status
DEL/api/orders/[id]Delete order record
GET/api/customersList all customers with filters
POST/api/customersAdd new customer record
DEL/api/customers/[id]Remove customer
GET/api/subscribersList subscribers with list filter
POST/api/subscribersAdd subscriber from website form
GET/api/contactsList contact form submissions
POST/api/contactsSubmit contact form (public)
POST/api/broadcastSend email to subscriber segment via SES
POST/api/webhooks/squareSquare payment webhook — auto-create orders
GET/api/analyticsRevenue stats for dashboard charts
src/app/api/orders/route.ts
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).

src/lib/auth.ts
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.

Test first. Use Square Sandbox credentials during development. Switch to production keys only after end-to-end testing is complete. Test card: 4111 1111 1111 1111
src/app/api/webhooks/square/route.ts
import { 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.

src/lib/ses.ts
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.

Pages to Build
1

/dashboard

Overview: revenue cards, monthly chart, format doughnut, recent orders table, recent subscribers. Matches admin.html overview view.

2

/dashboard/orders

All orders table with search, filter by format/status, pagination, CSV export. Detail modal on row click.

3

/dashboard/customers

Customer directory with search, mailing list badges, format badge. Add customer form on side panel.

4

/dashboard/subscribers

Combined customers + standalone subscribers. Filter tabs by list (goals, book2, ecosystem, ooki). CSV export.

5

/dashboard/broadcast

Email composer with list selector, subject, message body, quick templates. Calls /api/broadcast which uses SES.

6

/dashboard/contacts

Contact form inbox. Mark as read/replied/archived. Delete. Export.

7

/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.

Never commit .env.local to git. Add to .gitignore immediately. Use Vercel environment variables for production values. Rotate all keys after any accidental exposure.
.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.

Infrastructure
S3 bucket created + files synced
CloudFront distribution live
ACM SSL certificate issued & attached
Route 53 hosted zone for sharifdyson.com
Network Solutions NS → Route 53
goalsbook.com registered + redirect live
AWS RDS PostgreSQL provisioned
SES in production mode (not sandbox)
[email protected] verified in SES
Application
Prisma migrations applied to RDS
Admin user seeded (bcrypt password)
All env vars set in Vercel
NextAuth session works end-to-end
Square sandbox order flow tested
Square → production keys
Webhook URL registered in Square
Order confirmation email tested
Admin password updated — Goals2026!SD ✅
Questions? The admin.html prototype at 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.