mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
chore(harness): make repository agent-ready
Add concise routed context, task-aware agent roles, focused skills, and structured learning promotion.\n\nUnify local development and workspace tooling, add layered test and E2E verification, harden MCP authentication, and gate deployment behind verification and explicit production promotion.
This commit is contained in:
1 parent
5a69bbc1a5
commit
01bb8225ae
125 files changed
+5078
-24529
No files matched your search
@@ -0,0 +1,14 @@
|
||||
# Admin application
|
||||
|
||||
Vite/React administration UI. Use existing layout, route, and Supabase client
|
||||
patterns before introducing abstractions.
|
||||
|
||||
- `pack_creator` manages permitted content; `super_admin` can access privileged
|
||||
user/admin/audit surfaces.
|
||||
- Enforce role checks in both route visibility and the backend/RLS boundary.
|
||||
- Administrative mutations must remain auditable.
|
||||
- Use `.maybeSingle()` when zero rows are valid.
|
||||
- Never expose credentials, tokens, or private user content unnecessarily.
|
||||
- Add accessible labels and a focused test for changed behavior.
|
||||
- Verify with `npm run lint -w @sauci/admin`, `npm run typecheck -w
|
||||
@sauci/admin`, and `npm run build -w @sauci/admin`.
|
||||
@@ -1,84 +0,0 @@
|
||||
# Admin Dashboard - Agent Guidelines
|
||||
|
||||
**STRICT RULES - DO NOT VIOLATE**
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a React Admin dashboard using:
|
||||
- **Build**: Vite
|
||||
- **Routing**: react-router-dom with component-based routing in `App.tsx`
|
||||
- **Styling**: Tailwind CSS
|
||||
- **State**: React Context (`AuthContext`)
|
||||
- **Backend**: Supabase client in `src/lib/supabase.ts`
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
components/ # Reusable components
|
||||
layout/ # AppLayout, Sidebar, etc.
|
||||
ProtectedRoute.tsx # Route guard
|
||||
contexts/ # React contexts (AuthContext)
|
||||
hooks/ # Custom hooks
|
||||
lib/ # Utilities (supabase)
|
||||
pages/ # Page components
|
||||
content/ # Categories, Packs, Questions
|
||||
users/ # User management (super_admin only)
|
||||
admins/ # Admin management (super_admin only)
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
Two admin roles exist:
|
||||
- `admin` - Content management only (categories, packs, questions)
|
||||
- `super_admin` - Full access including user data, admins, audit logs
|
||||
|
||||
Use `<ProtectedRoute requireSuperAdmin>` for super_admin-only routes.
|
||||
|
||||
### Route Structure
|
||||
|
||||
```typescript
|
||||
// Public
|
||||
/login
|
||||
|
||||
// Protected (all admins)
|
||||
/ # Dashboard
|
||||
/categories # Category management
|
||||
/categories/:id/packs # Packs in category
|
||||
/packs/:id/questions # Questions in pack
|
||||
|
||||
// Protected (super_admin only)
|
||||
/users # User list
|
||||
/users/:id # User detail
|
||||
/users/:id/matches/:id # Match chat view
|
||||
/admins # Admin management
|
||||
/audit-logs # Audit log viewer
|
||||
```
|
||||
|
||||
### Supabase Queries
|
||||
|
||||
- **Always use `.maybeSingle()` instead of `.single()`** when the row might not exist
|
||||
- All admin operations are audited - check `audit_logs` table structure
|
||||
|
||||
### UI Patterns
|
||||
|
||||
- Use Tailwind CSS for styling
|
||||
- Follow existing component patterns in `src/components/`
|
||||
- Use `AppLayout` wrapper for all protected pages
|
||||
|
||||
### Security
|
||||
|
||||
- Never expose user passwords or sensitive data in admin views
|
||||
- Audit log all administrative actions
|
||||
- Verify admin role before performing operations
|
||||
|
||||
## Forbidden Actions
|
||||
|
||||
1. **DO NOT** expose user passwords or auth tokens
|
||||
2. **DO NOT** allow content admins to access user data
|
||||
3. **DO NOT** bypass role checks in protected routes
|
||||
4. **DO NOT** perform destructive operations without confirmation
|
||||
5. **DO NOT** use `.single()` for queries that might return 0 rows
|
||||
6. **DO NOT** skip audit logging for administrative actions
|
||||
@@ -4,9 +4,11 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Internal MCP service
|
||||
|
||||
Hono service exposing privileged administrative tools.
|
||||
|
||||
- Treat the service-role key and `SAUCI_MCP_API_KEY` as secrets.
|
||||
- Fail closed when MCP authentication is missing outside explicit local mode.
|
||||
- Every mutation validates inputs, authorizes the operation, and writes audit
|
||||
evidence where the product contract requires it.
|
||||
- Read operations preserve least privilege and avoid excessive user data.
|
||||
- Add unit/API coverage for tool contract or auth changes.
|
||||
- Verify lint, tests, typecheck, build, and `/health` locally.
|
||||
Generated
-2785
File diff suppressed because it is too large.
Load diff
@@ -5,10 +5,13 @@
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsup src/index.ts src/bridge.ts --format esm --clean",
|
||||
"lint": "tsc --noEmit",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"start": "node dist/index.mjs",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/node-server": "^1.13.7",
|
||||
"@hono/mcp": "^0.1.1",
|
||||
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||
"@supabase/supabase-js": "^2.45.0",
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Hono } from 'hono';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { authMiddleware } from './auth.js';
|
||||
|
||||
function app() {
|
||||
const instance = new Hono();
|
||||
instance.use('/mcp', authMiddleware);
|
||||
instance.get('/mcp', (c) => c.json({ ok: true }));
|
||||
return instance;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.SAUCI_MCP_API_KEY;
|
||||
});
|
||||
|
||||
describe('MCP authentication', () => {
|
||||
it('fails closed when the server key is missing', async () => {
|
||||
const response = await app().request('/mcp');
|
||||
expect(response.status).toBe(503);
|
||||
});
|
||||
|
||||
it('requires and validates a bearer token', async () => {
|
||||
process.env.SAUCI_MCP_API_KEY = 'local-test-key';
|
||||
expect((await app().request('/mcp')).status).toBe(401);
|
||||
expect((await app().request('/mcp', {
|
||||
headers: { Authorization: 'Bearer wrong' },
|
||||
})).status).toBe(401);
|
||||
expect((await app().request('/mcp', {
|
||||
headers: { Authorization: 'Bearer local-test-key' },
|
||||
})).status).toBe(200);
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,20 @@
|
||||
import { Context, Next } from 'hono';
|
||||
|
||||
export const authMiddleware = async (c: Context, next: Next) => {
|
||||
const authHeader = c.req.header('Authorization');
|
||||
const apiKey = process.env.SAUCI_MCP_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
// If no API key is set in env, allow access (warn in logs)
|
||||
// Ideally this should default to deny, but for dev ease we might want to warn
|
||||
console.warn('SAUCI_MCP_API_KEY not set in environment - allowing all requests');
|
||||
return next();
|
||||
console.error('SAUCI_MCP_API_KEY is not configured; refusing MCP access');
|
||||
return c.json({ error: 'Service authentication is not configured' }, 503);
|
||||
}
|
||||
|
||||
let token = c.req.header('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
token = c.req.query('apiKey');
|
||||
}
|
||||
const authHeader = c.req.header('Authorization');
|
||||
const token = authHeader?.startsWith('Bearer ')
|
||||
? authHeader.slice('Bearer '.length)
|
||||
: undefined;
|
||||
|
||||
if (!token) {
|
||||
return c.json({ error: 'Unauthorized: Missing or invalid Authorization header or apiKey query param' }, 401);
|
||||
return c.json({ error: 'Unauthorized: Bearer token required' }, 401);
|
||||
}
|
||||
|
||||
if (token !== apiKey) {
|
||||
|
||||
@@ -2,4 +2,10 @@
|
||||
module.exports = {
|
||||
extends: 'expo',
|
||||
ignorePatterns: ['/dist/*'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['plugins/**/*.js'],
|
||||
env: { node: true },
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
# Mobile application
|
||||
|
||||
Expo React Native with Expo Router, Zustand, Supabase, RevenueCat, Firebase, and
|
||||
native iOS/Android targets.
|
||||
|
||||
- Read `DESIGN.md` before UI changes.
|
||||
- Routes belong under `app`; feature logic belongs under `src/features`.
|
||||
- Reuse existing Zustand stores and clear user-scoped state during sign-out.
|
||||
- Check RevenueCat availability before native purchase operations.
|
||||
- Use `.maybeSingle()` when zero rows are valid and handle nullable profiles.
|
||||
- Keep route files thin; extract focused components/hooks when responsibilities
|
||||
diverge rather than enforcing a mechanical line limit.
|
||||
- Add stable accessibility labels or `testID` values for critical interactions.
|
||||
- Verify focused Jest tests, typecheck, lint, then native/Expo behavior as risk
|
||||
requires. UI work needs observable simulator evidence.
|
||||
@@ -1,73 +0,0 @@
|
||||
# Mobile App - Agent Guidelines
|
||||
|
||||
**STRICT RULES - DO NOT VIOLATE**
|
||||
|
||||
## Architecture
|
||||
|
||||
This is an Expo React Native app using:
|
||||
- **Routing**: expo-router with file-based routing in `app/` directory
|
||||
- **State**: Zustand stores in `src/store/index.ts`
|
||||
- **Backend**: Supabase client in `src/lib/supabase.ts`
|
||||
|
||||
Design guidance lives in `apps/mobile/DESIGN.md`.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
app/ # Expo Router routes
|
||||
(auth)/ # Authentication screens
|
||||
(app)/ # Protected app screens (tabs)
|
||||
src/
|
||||
components/ # Reusable components
|
||||
ui/
|
||||
lib/ # Utilities (supabase, analytics, revenuecat)
|
||||
store/ # Zustand stores (index.ts)
|
||||
theme/
|
||||
types/ # TypeScript types
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### File Size
|
||||
|
||||
- Keep files small: aim for 200-300 lines max per file.
|
||||
|
||||
### State Management
|
||||
|
||||
- Use existing Zustand stores: `useAuthStore`, `useMatchStore`, `usePacksStore`, `useMessageStore`, `useSubscriptionStore`
|
||||
- Access cross-store state via `getState()` (e.g., `useAuthStore.getState().user`)
|
||||
- Clear related stores on sign out (see `signOut` in `useAuthStore`)
|
||||
|
||||
### Supabase Queries
|
||||
|
||||
- **Always use `.maybeSingle()` instead of `.single()`** when the row might not exist
|
||||
- `.single()` throws an error if 0 or >1 rows are returned
|
||||
- Check for null before performing operations
|
||||
|
||||
### Response Handling
|
||||
|
||||
- Responses use UPSERT with `onConflict: "user_id,question_id"`
|
||||
- Match detection happens in `submit-response` edge function
|
||||
|
||||
### Typing
|
||||
|
||||
- Import shared types from `@/types`
|
||||
- Import Supabase types from `@/types/supabase`
|
||||
|
||||
### RevenueCat
|
||||
|
||||
- RevenueCat service in `src/lib/revenuecat.ts`
|
||||
- Does not work in Expo Go - check `revenueCatService.isAvailable()` first
|
||||
- Initialize with user ID after auth
|
||||
|
||||
### Analytics
|
||||
|
||||
- Use `Events` from `src/lib/analytics` for tracking
|
||||
- Track: `packEnabled`, `packDisabled`, `signOut`, etc.
|
||||
|
||||
## Forbidden Actions
|
||||
|
||||
1. **DO NOT** use `.single()` for queries that might return 0 rows
|
||||
2. **DO NOT** create new stores without integrating with auth sign-out cleanup
|
||||
3. **DO NOT** bypass RevenueCat availability checks
|
||||
4. **DO NOT** add navigation logic outside of expo-router
|
||||
Generated
-16696
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,25 @@
|
||||
# Supabase backend
|
||||
|
||||
This directory owns local configuration, migrations, Edge Functions, seeds, and
|
||||
integration tests.
|
||||
|
||||
## Schema safety
|
||||
|
||||
- Create migrations only with `npm run db:migration:new -- <name>`.
|
||||
- Edit only `apps/supabase/migrations/`; this is the authoritative migration tree.
|
||||
- Never use any remote `apply_migration` tool.
|
||||
- Never execute DDL through MCP SQL tools; those tools are `SELECT`-only.
|
||||
- Never link or reset a remote project during local verification.
|
||||
- Cron/function URLs must resolve per environment, never hardcode production.
|
||||
|
||||
## Function boundaries
|
||||
|
||||
- Set deployment JWT configuration deliberately per function. A function using
|
||||
`verify_jwt = false` must implement the correct manual, webhook, cron, or internal
|
||||
authentication boundary; it is not permission to omit authorization.
|
||||
- Service-role clients never run in customer clients.
|
||||
- Preserve CORS handling and return non-sensitive errors.
|
||||
- Test RLS, authorization, and important side effects, not only HTTP status.
|
||||
|
||||
Verify with local reset, `npm run test:supabase`, invariant lint, and affected
|
||||
client tests. See `docs/operations/database-migrations.md`.
|
||||
@@ -1,136 +0,0 @@
|
||||
# Supabase - Agent Guidelines
|
||||
|
||||
**STRICT RULES - DO NOT VIOLATE**
|
||||
|
||||
## Architecture
|
||||
|
||||
This contains Supabase configuration:
|
||||
- **Migrations**: SQL migrations in `migrations/`
|
||||
- **Edge Functions**: Deno functions in `functions/`
|
||||
- **Email Templates**: Auth email templates in `email-templates/`
|
||||
- **Config**: Local development config in `config.toml`
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
functions/
|
||||
delete-relationship/ # Delete couple relationship
|
||||
manage-couple/ # Join/leave couple
|
||||
revenuecat-webhook/ # Subscription webhook handler
|
||||
send-message-notification/ # Push notification for messages
|
||||
send-notification/ # Push notification for matches
|
||||
submit-response/ # Save response, check matches
|
||||
sync-subscription/ # Sync RevenueCat status
|
||||
migrations/ # SQL migrations
|
||||
email-templates/ # Auth email templates
|
||||
config.toml # Local dev configuration
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### Edge Function Deployment
|
||||
|
||||
**ALWAYS use `verify_jwt: false`** when deploying via MCP tools.
|
||||
|
||||
Supabase's built-in JWT verification causes 401 errors. Handle auth manually:
|
||||
|
||||
```typescript
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(
|
||||
authHeader.replace("Bearer ", "")
|
||||
);
|
||||
```
|
||||
|
||||
### Edge Function Pattern
|
||||
|
||||
All edge functions MUST follow this structure:
|
||||
|
||||
```typescript
|
||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
// Handle CORS preflight
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
|
||||
);
|
||||
|
||||
// Manual auth verification
|
||||
const authHeader = req.headers.get("Authorization");
|
||||
if (!authHeader) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Missing authorization header" }),
|
||||
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
const { data: { user }, error: authError } = await supabase.auth.getUser(
|
||||
authHeader.replace("Bearer ", "")
|
||||
);
|
||||
|
||||
// ... function logic
|
||||
} catch (error) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: "Internal server error" }),
|
||||
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Function Auth Methods
|
||||
|
||||
| Function | Auth Method |
|
||||
|----------|-------------|
|
||||
| `manage-couple` | Manual via getUser() |
|
||||
| `submit-response` | Manual via getUser() |
|
||||
| `sync-subscription` | Manual via getUser() |
|
||||
| `delete-relationship` | Manual via getUser() |
|
||||
| `revenuecat-webhook` | Webhook signature validation |
|
||||
| `send-notification` | Internal trigger (no auth) |
|
||||
| `send-message-notification` | Internal trigger (no auth) |
|
||||
|
||||
### Migration Rules
|
||||
|
||||
- Use `apply_migration` MCP tool for DDL operations
|
||||
- Never hardcode generated IDs in data migrations
|
||||
- Always verify target environment (prod vs non-prod) before running
|
||||
|
||||
### Database Patterns
|
||||
|
||||
- Use UPSERT with `onConflict` for responses: `onConflict: "user_id,question_id"`
|
||||
- Use UPSERT for matches: `onConflict: "couple_id,question_id"`
|
||||
- RLS policies enforce couple-level data isolation
|
||||
|
||||
### Profile Creation
|
||||
|
||||
The `on_auth_user_created` trigger automatically creates a profile. Never assume a profile exists without checking - check for null before operations.
|
||||
|
||||
## MCP Environments
|
||||
|
||||
| Environment | MCP Server |
|
||||
|-------------|------------|
|
||||
| Production | `mcp__sauci-prod__*` |
|
||||
| Non-Production | `mcp__sauci-non-prod__*` |
|
||||
|
||||
**ALWAYS verify environment before destructive operations.**
|
||||
|
||||
## Forbidden Actions
|
||||
|
||||
1. **DO NOT** use `verify_jwt: true` when deploying functions via MCP
|
||||
2. **DO NOT** skip CORS headers in responses
|
||||
3. **DO NOT** use service role key on client-side
|
||||
4. **DO NOT** hardcode IDs in migrations
|
||||
5. **DO NOT** assume profiles exist - always check for null
|
||||
6. **DO NOT** run migrations on production without explicit confirmation
|
||||
7. **DO NOT** expose internal error details in responses
|
||||
@@ -1,5 +1,11 @@
|
||||
{
|
||||
"name": "@sauci/supabase",
|
||||
"version": "1.0.0",
|
||||
"private": true
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "node ../../scripts/lint-harness.mjs --scope supabase",
|
||||
"test": "node -e \"console.log('Supabase integration tests run through npm run test:supabase')\"",
|
||||
"typecheck": "node -e \"console.log('Edge Functions are checked by Deno during test:supabase')\"",
|
||||
"build": "node -e \"console.log('Supabase deployables do not require a build step')\""
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ export async function createRedemptionCode() {
|
||||
const { data, error } = await adminClient.from("redemption_codes").insert({
|
||||
code,
|
||||
max_uses: 1,
|
||||
active: true,
|
||||
is_active: true,
|
||||
expires_at: new Date(Date.now() + 3600000).toISOString() // 1 hour
|
||||
}).select().single();
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Web application
|
||||
|
||||
Next.js App Router marketing, legal, and redemption UI.
|
||||
|
||||
- Prefer server components; add `use client` only when browser state is needed.
|
||||
- Preserve PostHog provider/page-view integration unless the task changes it.
|
||||
- Keep metadata current for public pages.
|
||||
- Never expose server credentials through `NEXT_PUBLIC_*` variables.
|
||||
- Public redemption must use sandbox/local Supabase during automated tests.
|
||||
- Verify lint, typecheck, build, and the relevant Playwright flow.
|
||||
@@ -1,86 +0,0 @@
|
||||
# Web App - Agent Guidelines
|
||||
|
||||
**STRICT RULES - DO NOT VIOLATE**
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a Next.js 14+ marketing/landing website using:
|
||||
- **Framework**: Next.js with App Router
|
||||
- **Styling**: Tailwind CSS
|
||||
- **Analytics**: PostHog
|
||||
- **Routing**: File-based routing in `app/` directory
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
app/
|
||||
layout.tsx # Root layout with PostHog provider
|
||||
page.tsx # Home/landing page
|
||||
globals.css # Global styles
|
||||
providers.tsx # PostHog provider wrapper
|
||||
PostHogPageView.tsx # Page view tracking component
|
||||
privacy/ # Privacy policy
|
||||
terms/ # Terms of service
|
||||
redeem/ # Code redemption page
|
||||
components/ # Reusable components
|
||||
public/ # Static assets (images, fonts)
|
||||
```
|
||||
|
||||
## Critical Rules
|
||||
|
||||
### App Router
|
||||
|
||||
This uses Next.js App Router (not Pages Router):
|
||||
- Server Components by default
|
||||
- Use `'use client'` directive for client components
|
||||
- Metadata exported from layout.tsx and page.tsx
|
||||
|
||||
### PostHog Integration
|
||||
|
||||
PostHog is configured at root level:
|
||||
- Provider in `providers.tsx`
|
||||
- Page view tracking in `PostHogPageView.tsx`
|
||||
- Wrap all pages in `<PostHogProvider>`
|
||||
|
||||
### SEO & Metadata
|
||||
|
||||
Metadata is defined in `layout.tsx`:
|
||||
```typescript
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sauci - Ignite Your Connection',
|
||||
description: '...',
|
||||
openGraph: { ... },
|
||||
twitter: { ... },
|
||||
};
|
||||
```
|
||||
|
||||
Update metadata when adding new pages.
|
||||
|
||||
### Styling
|
||||
|
||||
- Use Tailwind CSS classes
|
||||
- Global styles in `globals.css`
|
||||
- Follow mobile-first responsive design
|
||||
|
||||
### Static Pages
|
||||
|
||||
Privacy and Terms pages are static content:
|
||||
- `/privacy` - Privacy policy
|
||||
- `/terms` - Terms of service
|
||||
|
||||
Keep these updated with legal requirements.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Required environment variables:
|
||||
- `NEXT_PUBLIC_POSTHOG_KEY` - PostHog API key
|
||||
- `NEXT_PUBLIC_POSTHOG_HOST` - PostHog host
|
||||
|
||||
## Forbidden Actions
|
||||
|
||||
1. **DO NOT** remove PostHog tracking without approval
|
||||
2. **DO NOT** use Pages Router patterns (use App Router)
|
||||
3. **DO NOT** add server-side API routes that expose sensitive data
|
||||
4. **DO NOT** forget to update metadata for new pages
|
||||
5. **DO NOT** skip mobile responsiveness
|
||||
6. **DO NOT** commit .env files with real API keys
|
||||
@@ -120,7 +120,7 @@ export default function Pricing() {
|
||||
<p className="text-sm text-white/80">
|
||||
<span className="font-semibold text-white">Pay once per couple.</span>{' '}
|
||||
Whether monthly or yearly, one subscription unlocks Pro for both you and your partner.
|
||||
That's two accounts for the price of one.
|
||||
That's two accounts for the price of one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,7 +153,7 @@ export default function Pricing() {
|
||||
transition={{ delay: 0.3 }}
|
||||
className="text-center text-white/40 text-sm mt-8 max-w-lg mx-auto"
|
||||
>
|
||||
Start free with access to selected packs. Upgrade to Pro when you're ready to unlock everything.
|
||||
Start free with access to selected packs. Upgrade to Pro when you're ready to unlock everything.
|
||||
</motion.p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Generated
-1798
File diff suppressed because it is too large.
Load diff
@@ -5,9 +5,10 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^11.0.0",
|
||||
@@ -20,9 +21,11 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"postcss": "^8.4.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-next": "14.2.35",
|
||||
"postcss": "^8.4.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user