mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-02 02:19:43 +00:00
fix(web): add account deletion page (#16)
Add the public deletion resource and repair the monorepo web container build contract.
This commit is contained in:
11 files changed
+162
-14
No files matched your search
@@ -1,5 +1,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
**/node_modules
|
**/node_modules
|
||||||
|
.next
|
||||||
|
**/.next
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
*.md
|
*.md
|
||||||
@@ -23,11 +25,15 @@ apps/mcp
|
|||||||
apps/mobile
|
apps/mobile
|
||||||
apps/supabase
|
apps/supabase
|
||||||
apps/web
|
apps/web
|
||||||
|
!apps/web
|
||||||
|
!apps/web/**
|
||||||
e2e
|
e2e
|
||||||
evidence
|
evidence
|
||||||
etc
|
etc
|
||||||
infra
|
infra
|
||||||
scripts
|
scripts
|
||||||
|
!scripts
|
||||||
|
!scripts/fix-hoisting.js
|
||||||
terraform
|
terraform
|
||||||
.agents
|
.agents
|
||||||
.claude
|
.claude
|
||||||
|
|||||||
+10
-12
@@ -12,15 +12,12 @@ ENV NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY
|
|||||||
ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
|
ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
|
||||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies from the committed monorepo lockfile.
|
||||||
COPY package*.json ./
|
COPY . .
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
# Copy source code
|
# Build the web workspace from the repository root.
|
||||||
COPY . .
|
RUN npm run build -w @sauci/web
|
||||||
|
|
||||||
# Build the application
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM node:20-alpine AS runner
|
FROM node:20-alpine AS runner
|
||||||
@@ -37,10 +34,11 @@ RUN apk add --no-cache curl
|
|||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
# Copy built assets from builder
|
# Next preserves the workspace path in standalone output. Keep that layout so
|
||||||
COPY --from=builder /app/public ./public
|
# the generated server can resolve its .next directory and public assets.
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
@@ -49,4 +47,4 @@ EXPOSE 3000
|
|||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "apps/web/server.js"]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
const pagePath = new URL('./page.tsx', import.meta.url)
|
||||||
|
|
||||||
|
describe('/delete-account public deletion resource', () => {
|
||||||
|
it('provides an accessible request route and the required deletion details', async () => {
|
||||||
|
const page = await readFile(pagePath, 'utf8')
|
||||||
|
|
||||||
|
expect(page).toContain('Delete your Sauci account')
|
||||||
|
expect(page).toContain('mailto:privacy@sauci.app')
|
||||||
|
expect(page).toContain('without reinstalling the app')
|
||||||
|
expect(page).toContain('Open Sauci and go to your Profile.')
|
||||||
|
expect(page).toContain('Choose Delete Account and follow the confirmation steps.')
|
||||||
|
expect(page).toContain('within 30 days')
|
||||||
|
expect(page).toContain('required for legal purposes')
|
||||||
|
expect(page).toContain('relationship data, question responses, match data, chat messages and photos')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import Link from 'next/link'
|
||||||
|
import { ArrowLeft } from 'lucide-react'
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Delete your Sauci account',
|
||||||
|
description: 'How to delete your Sauci account and associated data.',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DeleteAccountPage() {
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen bg-gradient-to-b from-background-light to-background">
|
||||||
|
<header className="py-6 px-6 border-b border-white/5">
|
||||||
|
<div className="max-w-4xl mx-auto flex items-center gap-4">
|
||||||
|
<Link href="/" className="flex items-center gap-2 text-white/60 hover:text-white transition-colors">
|
||||||
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
<span>Back to Home</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-12">
|
||||||
|
<h1 className="text-4xl font-bold mb-4">Delete your Sauci account</h1>
|
||||||
|
<p className="text-white/70 leading-relaxed text-lg">
|
||||||
|
You can request deletion of your Sauci account and associated data without reinstalling the app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="glass p-6 my-8">
|
||||||
|
<h2 className="text-2xl font-semibold text-white mb-3">Request deletion by email</h2>
|
||||||
|
<p className="text-white/70 leading-relaxed mb-4">
|
||||||
|
Email us from the address associated with your Sauci account and ask us to delete your account.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="mailto:privacy@sauci.app?subject=Sauci%20account%20deletion%20request"
|
||||||
|
className="inline-flex rounded-lg bg-primary px-5 py-3 font-semibold text-white hover:opacity-90 transition-opacity"
|
||||||
|
>
|
||||||
|
Request account deletion
|
||||||
|
</a>
|
||||||
|
<p className="text-white/60 mt-4">
|
||||||
|
Or email <a href="mailto:privacy@sauci.app" className="text-primary hover:underline">privacy@sauci.app</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="prose prose-invert prose-lg max-w-none space-y-8">
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold text-white mb-4">Delete your account in the Sauci app</h2>
|
||||||
|
<ol className="list-decimal pl-6 text-white/70 space-y-2">
|
||||||
|
<li>Open Sauci and go to your Profile.</li>
|
||||||
|
<li>Select Account.</li>
|
||||||
|
<li>Choose Delete Account and follow the confirmation steps.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold text-white mb-4">What deletion covers</h2>
|
||||||
|
<p className="text-white/70 leading-relaxed">
|
||||||
|
Deleting an account permanently removes the account and profile, relationship data, question responses, match data, chat messages and photos, encryption keys, and other cloud-stored data associated with that account.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold text-white mb-4">Timing and limited retention</h2>
|
||||||
|
<p className="text-white/70 leading-relaxed">
|
||||||
|
We will delete or anonymize your personal information within 30 days. We may retain information where required for legal purposes.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-semibold text-white mb-4">Need help?</h2>
|
||||||
|
<p className="text-white/70 leading-relaxed">
|
||||||
|
For questions about deletion or your data, contact <a href="mailto:privacy@sauci.app" className="text-primary hover:underline">privacy@sauci.app</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className="py-8 px-6 border-t border-white/5">
|
||||||
|
<div className="max-w-4xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<span className="text-xl font-bold gradient-text">Sauci</span>
|
||||||
|
<div className="flex gap-6 text-sm text-white/40">
|
||||||
|
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
||||||
|
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
||||||
|
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -132,6 +132,9 @@ export default function PrivacyPolicy() {
|
|||||||
<p className="text-white/70 leading-relaxed mt-4">
|
<p className="text-white/70 leading-relaxed mt-4">
|
||||||
To exercise these rights, please contact us at <a href="mailto:privacy@sauci.app" className="text-primary hover:underline">privacy@sauci.app</a>.
|
To exercise these rights, please contact us at <a href="mailto:privacy@sauci.app" className="text-primary hover:underline">privacy@sauci.app</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<p className="text-white/70 leading-relaxed mt-4">
|
||||||
|
For account deletion instructions and an email deletion request, visit our <Link href="/delete-account" className="text-primary hover:underline">account deletion page</Link>.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@@ -176,6 +179,7 @@ export default function PrivacyPolicy() {
|
|||||||
<span className="text-xl font-bold gradient-text">Sauci</span>
|
<span className="text-xl font-bold gradient-text">Sauci</span>
|
||||||
<div className="flex gap-6 text-sm text-white/40">
|
<div className="flex gap-6 text-sm text-white/40">
|
||||||
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
||||||
|
<Link href="/delete-account" className="hover:text-white transition-colors">Delete Account</Link>
|
||||||
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
||||||
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ export default function RedeemPage() {
|
|||||||
<span className="text-xl font-bold gradient-text">Sauci</span>
|
<span className="text-xl font-bold gradient-text">Sauci</span>
|
||||||
<div className="flex gap-6 text-sm text-white/40">
|
<div className="flex gap-6 text-sm text-white/40">
|
||||||
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
||||||
|
<Link href="/delete-account" className="hover:text-white transition-colors">Delete Account</Link>
|
||||||
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
||||||
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ export default function TermsAndConditions() {
|
|||||||
<span className="text-xl font-bold gradient-text">Sauci</span>
|
<span className="text-xl font-bold gradient-text">Sauci</span>
|
||||||
<div className="flex gap-6 text-sm text-white/40">
|
<div className="flex gap-6 text-sm text-white/40">
|
||||||
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
<Link href="/privacy" className="hover:text-white transition-colors">Privacy</Link>
|
||||||
|
<Link href="/delete-account" className="hover:text-white transition-colors">Delete Account</Link>
|
||||||
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
<Link href="/terms" className="hover:text-white transition-colors">Terms</Link>
|
||||||
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
<Link href="/" className="hover:text-white transition-colors">Home</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export default function Footer() {
|
|||||||
<Link href="/privacy" className="text-white/60 hover:text-white transition-colors text-sm">
|
<Link href="/privacy" className="text-white/60 hover:text-white transition-colors text-sm">
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link href="/delete-account" className="text-white/60 hover:text-white transition-colors text-sm">
|
||||||
|
Delete Account
|
||||||
|
</Link>
|
||||||
<Link href="/terms" className="text-white/60 hover:text-white transition-colors text-sm">
|
<Link href="/terms" className="text-white/60 hover:text-white transition-colors text-sm">
|
||||||
Terms & Conditions
|
Terms & Conditions
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ version: '3.8'
|
|||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ../..
|
||||||
dockerfile: Dockerfile
|
dockerfile: apps/web/Dockerfile
|
||||||
args:
|
args:
|
||||||
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
|
- NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
|
||||||
- NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST:-https://eu.i.posthog.com}
|
- NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST:-https://eu.i.posthog.com}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ historical notes. Agents should load only the section relevant to their task.
|
|||||||
- [Testing strategy](testing/strategy.md)
|
- [Testing strategy](testing/strategy.md)
|
||||||
- [Database migration safety](operations/database-migrations.md)
|
- [Database migration safety](operations/database-migrations.md)
|
||||||
- [Backend deployment and branching](operations/backend-deployments.md)
|
- [Backend deployment and branching](operations/backend-deployments.md)
|
||||||
|
- [Website deployment](operations/web-deployments.md)
|
||||||
- [Agent routing and effort](agents/routing.md)
|
- [Agent routing and effort](agents/routing.md)
|
||||||
- [Learning promotion](agents/learnings.md)
|
- [Learning promotion](agents/learnings.md)
|
||||||
- [Authentication](authentication.md)
|
- [Authentication](authentication.md)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Website deployment
|
||||||
|
|
||||||
|
Dokploy deploys the public Sauci website from the `main` branch using
|
||||||
|
`apps/web/docker-compose.prod.yml`. The Compose file builds from the repository
|
||||||
|
root so the Docker build can use the committed workspace lockfile. Its Dockerfile
|
||||||
|
builds `@sauci/web` and runs the Next standalone server from its workspace path.
|
||||||
|
|
||||||
|
## Required configuration
|
||||||
|
|
||||||
|
`NEXT_PUBLIC_API_URL` is required at build time and must be the intended public
|
||||||
|
standalone API URL. `NEXT_PUBLIC_POSTHOG_KEY` is optional. When it is set,
|
||||||
|
`NEXT_PUBLIC_POSTHOG_HOST` defaults to `https://eu.i.posthog.com` unless Dokploy
|
||||||
|
provides a different value. These public values are inlined into the Next build;
|
||||||
|
do not place server credentials in them.
|
||||||
|
|
||||||
|
## Promotion and acceptance
|
||||||
|
|
||||||
|
Promote the verified website commit to `main` before allowing Dokploy to deploy
|
||||||
|
it. After the deployment reports success, verify valid TLS and HTTP 200 for:
|
||||||
|
|
||||||
|
- `https://sauci.app/`
|
||||||
|
- `https://sauci.app/delete-account`
|
||||||
|
|
||||||
|
The second check is required for the Google Play account-deletion resource. A
|
||||||
|
successful Dokploy deployment alone is not sufficient evidence that either URL
|
||||||
|
is publicly reachable.
|
||||||
Reference in new issue
Block a user