'use client'; import { useState } from 'react'; import Link from 'next/link'; import { toast } from 'sonner'; import ProposalForm, { type ProposalFormData } from '@/components/ProposalForm'; import { Card } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { generateKey, encryptField, encryptOptional } from '@/lib/crypto-client'; interface Links { publicUrl: string; manageUrl: string; } // Encrypts every personal/narrative field client-side before it ever reaches // the network; the server only ever receives and stores ciphertext. The key // itself is generated here and never sent to the server; it only travels in // the URL fragment of the links shown to the user. async function encryptPayload(key: string, data: ProposalFormData) { const activities = await Promise.all( data.activities.map(async (a) => ({ title: await encryptField(key, a.title), description: await encryptOptional(key, a.description), location: await encryptOptional(key, a.location), emoji: a.emoji, slots: await Promise.all( a.slots.map(async (s) => ({ label: await encryptField(key, s.label), startsAt: await encryptOptional(key, s.startsAt), })) ), })) ); return { senderName: await encryptField(key, data.senderName), recipientName: await encryptField(key, data.recipientName), title: await encryptField(key, data.title), message: await encryptField(key, data.message), theme: data.theme, gradientFrom: data.gradientFrom, gradientVia: data.gradientVia, gradientTo: data.gradientTo, gifUrl: await encryptOptional(key, data.gifUrl), evasiveNo: data.evasiveNo, activities, }; } export default function CreateClient() { const [links, setLinks] = useState(null); const [error, setError] = useState(''); async function handleSubmit(data: ProposalFormData, _publish: boolean) { setError(''); if (typeof crypto === 'undefined' || !crypto.subtle) { setError('Your browser blocked secure encryption here (this page needs HTTPS). Try opening the site directly instead of through an in-app browser.'); return; } try { const key = await generateKey(); const payload = await encryptPayload(key, data); const res = await fetch('/api/proposals/guest', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const json = await res.json(); if (!res.ok) { setError('Something went wrong. Please check your inputs and try again.'); return; } setLinks({ publicUrl: `${json.publicUrl}#k=${key}`, manageUrl: `${json.manageUrl}#k=${key}`, }); } catch { setError('Something went wrong creating your proposal. Please try again.'); } } function copyLink(url: string) { navigator.clipboard.writeText(url); toast.success('Link copied'); } if (links) { return (
🎉

Your proposal is ready!

Share the first link, keep the second one private.

copyLink(links.publicUrl)} highlight /> copyLink(links.manageUrl)} />

Want to create another one?{' '} Make a new proposal →

); } return ; } function LinkBox({ emoji, label, sublabel, url, onCopy, highlight, }: { emoji: string; label: string; sublabel: string; url: string; onCopy: () => void; highlight?: boolean; }) { return (
{emoji}

{label}

{sublabel}

{url}
); }