add new GDPR page and handle account creation email
This commit is contained in:
@@ -11,11 +11,18 @@
|
|||||||
<div class="footer-inner px-8 py-16" class:sidebar>
|
<div class="footer-inner px-8 py-16" class:sidebar>
|
||||||
<div class="footer-row flex items-center justify-between gap-2">
|
<div class="footer-row flex items-center justify-between gap-2">
|
||||||
<span class="footer-star">✦</span>
|
<span class="footer-star">✦</span>
|
||||||
|
<div class="footer-center">
|
||||||
<span class="footer-copy"
|
<span class="footer-copy"
|
||||||
>© {new Date().getFullYear()}
|
>© {new Date().getFullYear()}
|
||||||
<span class="footer-text font-bold text-xl"><FamDone /></span> All rights reserved.</span
|
<span class="footer-text font-bold text-xl"><FamDone /></span> All rights reserved.</span
|
||||||
>
|
>
|
||||||
<aside class="footer-aside text-text-200 text-wash-300 flex flex-col justify-end text-right text-sm">
|
<span class="footer-links">
|
||||||
|
<a href="/privacy">Privacy Policy</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<aside
|
||||||
|
class="footer-aside text-text-200 text-wash-300 flex flex-col justify-end text-right text-sm"
|
||||||
|
>
|
||||||
<span>Built by humans</span>
|
<span>Built by humans</span>
|
||||||
<a href="https://threejjjs.xyz">
|
<a href="https://threejjjs.xyz">
|
||||||
<h5 class="m-0 text-right font-tertiary text-xl md:text-3xl">
|
<h5 class="m-0 text-right font-tertiary text-xl md:text-3xl">
|
||||||
@@ -53,6 +60,24 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
.footer-center {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer-links {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
.footer-links a {
|
||||||
|
color: #4c1d95;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer-links a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.footer-row {
|
.footer-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Code-specific email templates for successful access-code redemption.
|
||||||
|
// Key = the raw access-code value a family enters (e.g. "copppermill03").
|
||||||
|
// Add an entry here and its custom email is sent when that code is used.
|
||||||
|
// If no entry matches, `defaultAccessEmailTemplate` is used.
|
||||||
|
|
||||||
|
export interface AccessEmailVars {
|
||||||
|
famName: string;
|
||||||
|
codeName: string;
|
||||||
|
dashboardUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccessEmailTemplate {
|
||||||
|
subject: (vars: AccessEmailVars) => string;
|
||||||
|
html: (vars: AccessEmailVars) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TEMPLATE: AccessEmailTemplate = {
|
||||||
|
subject: ({ famName }) => `Your ${famName} access is unlocked`,
|
||||||
|
html: ({ famName, codeName, dashboardUrl }) => `
|
||||||
|
<p>Hi${famName ? ` ${famName}` : ' there'}! 🎉</p>
|
||||||
|
<p>Your access code (<strong>${codeName}</strong>) has been applied successfully.</p>
|
||||||
|
<p>Your <strong>${famName}</strong> family on <strong>FamDone</strong> is now unlocked.</p>
|
||||||
|
<p><a href="${dashboardUrl}">Open your family dashboard</a> to add kids, set up chores, and get going.</p>
|
||||||
|
<p>If you have any questions, just reply to this email.</p>
|
||||||
|
<p>Talk soon,<br />The FamDone team</p>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
export const accessEmailTemplates: Record<string, AccessEmailTemplate> = {
|
||||||
|
copppermill03: {
|
||||||
|
subject: ({ famName }) => `Save a spot for ${famName} — access unlocked`,
|
||||||
|
html: ({ famName, codeName, dashboardUrl }) => `
|
||||||
|
<p>Hi${famName ? ` ${famName}` : ' there'}! 🎉</p>
|
||||||
|
<p>Your access code (<strong>${codeName}</strong>) has been applied successfully.</p>
|
||||||
|
<p>Your <strong>${famName}</strong> family on <strong>FamDone</strong> is now unlocked.</p>
|
||||||
|
<p><a href="${dashboardUrl}">Open your family dashboard</a> to add kids, set up chores, and get going.</p>
|
||||||
|
<p>If you have any questions, just reply to this email.</p>
|
||||||
|
<p>Talk soon,<br />The FamDone team</p>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolve the template for a redeemed code, falling back to the default.
|
||||||
|
export function getAccessEmailTemplate(codeValue: string): AccessEmailTemplate {
|
||||||
|
return accessEmailTemplates[codeValue] || DEFAULT_TEMPLATE;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Resend } from 'resend';
|
import { Resend } from 'resend';
|
||||||
import { RESEND_API } from '$app/env/private';
|
import { RESEND_API } from '$app/env/private';
|
||||||
|
import { getAccessEmailTemplate } from './email-templates';
|
||||||
|
|
||||||
const resend = new Resend(String(RESEND_API));
|
const resend = new Resend(String(RESEND_API));
|
||||||
|
|
||||||
@@ -43,3 +44,53 @@ export async function sendParentInviteEmail(opts: {
|
|||||||
`
|
`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendAccessUnlockedEmail(opts: {
|
||||||
|
to: string;
|
||||||
|
famName: string;
|
||||||
|
codeName: string;
|
||||||
|
codeValue: string;
|
||||||
|
dashboardUrl: string;
|
||||||
|
}) {
|
||||||
|
const { to, famName, codeName, codeValue, dashboardUrl } = opts;
|
||||||
|
|
||||||
|
const template = getAccessEmailTemplate(codeValue);
|
||||||
|
const vars = { famName, codeName, dashboardUrl };
|
||||||
|
|
||||||
|
await resend.emails.send({
|
||||||
|
from: 'no-reply@famchamp.ai',
|
||||||
|
to,
|
||||||
|
subject: template.subject(vars),
|
||||||
|
html: template.html(vars)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendWelcomeEmail(opts: {
|
||||||
|
to: string;
|
||||||
|
famName: string;
|
||||||
|
dashboardUrl: string;
|
||||||
|
}) {
|
||||||
|
const { to, famName, dashboardUrl } = opts;
|
||||||
|
|
||||||
|
await resend.emails.send({
|
||||||
|
from: 'no-reply@famchamp.ai',
|
||||||
|
to,
|
||||||
|
subject: `Welcome to FamDone${famName ? `, ${famName}` : ''}!`,
|
||||||
|
html: `
|
||||||
|
<p>Hi${famName ? ` ${famName}` : ' there'}! 👋</p>
|
||||||
|
<p>Welcome to <strong>FamDone</strong> — your family chore and pocket-money app.</p>
|
||||||
|
<p>Your family is all set up. Here's how to get going:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Open your <a href="${dashboardUrl}">family dashboard</a>.</li>
|
||||||
|
<li>Add your kids and create join codes from <strong>Family Settings</strong>.</li>
|
||||||
|
<li>Set up chores, rewards, and allowances.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
If you haven't already, you can pick a plan and pay from your dashboard to
|
||||||
|
unlock your family.
|
||||||
|
</p>
|
||||||
|
<p>If you have any questions, just reply to this email — we're happy to help.</p>
|
||||||
|
<p>Talk soon,<br />The FamDone team</p>
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,10 +141,6 @@
|
|||||||
qrDataUrl = await QRCode.toDataURL(url, { width: 200, margin: 1 });
|
qrDataUrl = await QRCode.toDataURL(url, { width: 200, margin: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleParentInvite() {
|
|
||||||
alert('Parent invite coming soon — email would be sent to ' + parentInviteEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInviteResult(result: any) {
|
function handleInviteResult(result: any) {
|
||||||
if (result.type === 'success') {
|
if (result.type === 'success') {
|
||||||
const d = result.data as { ok?: boolean; error?: string } | undefined;
|
const d = result.data as { ok?: boolean; error?: string } | undefined;
|
||||||
|
|||||||
@@ -34,11 +34,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AuthShell
|
<AuthShell title="Log in" subtitle="Welcome back to FamDone." secondary={secondaryChild}>
|
||||||
title="Log in"
|
|
||||||
subtitle="Welcome back to FamDone."
|
|
||||||
secondary={secondaryChild}
|
|
||||||
>
|
|
||||||
{#if showForgot}
|
{#if showForgot}
|
||||||
{#if forgotSuccess}
|
{#if forgotSuccess}
|
||||||
<p class="form-ok">
|
<p class="form-ok">
|
||||||
@@ -93,6 +89,10 @@
|
|||||||
Forgot password?
|
Forgot password?
|
||||||
</button>
|
</button>
|
||||||
<button type="submit">Log in</button>
|
<button type="submit">Log in</button>
|
||||||
|
<p class="privacy-note">
|
||||||
|
We use a cookie to keep you signed in and collect only what we need to run your account. See
|
||||||
|
our <a href="/privacy">Privacy Policy</a>.
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p class="alt">
|
<p class="alt">
|
||||||
@@ -194,6 +194,21 @@
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.privacy-note {
|
||||||
|
margin: 0.25rem 0 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.privacy-note a {
|
||||||
|
color: #4338ca;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.privacy-note a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.alt a {
|
.alt a {
|
||||||
color: #4338ca;
|
color: #4338ca;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Footer from '$lib/components/Footer.svelte';
|
||||||
|
import FamDone from '$lib/components/FamDone.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Privacy Policy — FamDone</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="How FamDone collects, uses, and protects your personal data — in plain, GDPR-friendly language."
|
||||||
|
/>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<main class="privacy">
|
||||||
|
<a class="brand" href="/"><FamDone /></a>
|
||||||
|
<div class="card">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p class="updated">Last updated: September 2026</p>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>1. Who we are (the controller)</h2>
|
||||||
|
<p>
|
||||||
|
<FamDone /> is provided by <strong>threejjjs / Interactive Tech</strong>. We are the "data
|
||||||
|
controller" for the personal data you give us. If you have any questions about this policy
|
||||||
|
or your data, you can reach us at
|
||||||
|
<a href="mailto:privacy@threejjjs.xyz">privacy@threejjjs.xyz</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>2. What data we collect</h2>
|
||||||
|
<p>We keep things minimal. When you create an account we collect:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email address</strong> — used only as your login identity.</li>
|
||||||
|
<li>
|
||||||
|
<strong>Usernames and family/chore names</strong> — so your account and your family's chores
|
||||||
|
work.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
We do <strong>not</strong> collect any sensitive data (race, health, religion, biometrics, etc.),
|
||||||
|
and we do not use your data for profiling, advertising, or predictions about you.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>3. Why we collect it (the purpose & legal basis)</h2>
|
||||||
|
<p>
|
||||||
|
We process your data solely to provide the service you asked for: to create and maintain
|
||||||
|
your account, and to authenticate you when you log in. The legal basis for this is <strong
|
||||||
|
>contract necessity</strong
|
||||||
|
> (Article 6(1)(b) GDPR) — we process the data needed to run the account you signed up for.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>4. How long we keep it</h2>
|
||||||
|
<p>
|
||||||
|
We keep your data for as long as your account is active. If you delete your account, (see
|
||||||
|
"Your rights" below) we delete or anonymise your personal data in a reasonable timeframe,
|
||||||
|
unless we're legally required to keep it longer.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>5. Do we share or sell your data?</h2>
|
||||||
|
<p>
|
||||||
|
<strong>No.</strong> We do not sell, rent, or trade your personal data, and we do not share it
|
||||||
|
with third parties for their own marketing purposes. We only share the data that's strictly necessary
|
||||||
|
to run the service (for example, processing a payment you make), and we use your email only to
|
||||||
|
sign you in.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>6. Cookies and login</h2>
|
||||||
|
<p>
|
||||||
|
We use a strictly-necessary session/login cookie so you can stay signed in. This is
|
||||||
|
essential for the service to work and does not require separate consent. We do not use
|
||||||
|
cookies for advertising, analytics, or marketing pixels.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>7. Your rights</h2>
|
||||||
|
<p>Under the GDPR you have the right to:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Access</strong> the personal data we hold about you.</li>
|
||||||
|
<li><strong>Rectify</strong> inaccurate data.</li>
|
||||||
|
<li>Request <strong>deletion</strong> ("right to be forgotten") of your data.</li>
|
||||||
|
<li><strong>Portability</strong> — receive your data in a machine-readable format.</li>
|
||||||
|
<li>Withdraw consent or object to processing where it applies.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
To exercise any of these rights, email
|
||||||
|
<a href="mailto:privacy@threejjjs.xyz">privacy@threejjjs.xyz</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>8. International transfers</h2>
|
||||||
|
<p>
|
||||||
|
We store your data on our secure hosting providers. If your data is ever transferred outside
|
||||||
|
the European Economic Area (EEA), we rely on recognised safeguards (such as the relevant
|
||||||
|
adequacy decisions or the EU's Standard Contractual Clauses) to protect it.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>9. Complaints</h2>
|
||||||
|
<p>
|
||||||
|
If you believe we've handled your data incorrectly, please contact us first at
|
||||||
|
<a href="mailto:privacy@threejjjs.xyz">privacy@threejjjs.xyz</a>. You also have the right to
|
||||||
|
lodge a complaint with your local supervisory authority at any time.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>10. Children's data</h2>
|
||||||
|
<p>
|
||||||
|
A family can add child members under a parent's account. We only keep the child's chosen
|
||||||
|
username so their chores and progress work. As a parent you control this data and can change
|
||||||
|
or delete it from your family settings or by contacting us.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>11. Contact</h2>
|
||||||
|
<p>
|
||||||
|
Questions about this policy or your data? Email
|
||||||
|
<a href="mailto:privacy@threejjjs.xyz">privacy@threejjjs.xyz</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
main {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem 1rem 0;
|
||||||
|
background: linear-gradient(135deg, #eef2ff 0%, #ffffff 55%, #eef2ff 100%);
|
||||||
|
font-family:
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
sans-serif;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
.brand {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #4338ca;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 720px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 12px 30px rgba(67, 56, 202, 0.08);
|
||||||
|
padding: 2.5rem;
|
||||||
|
}
|
||||||
|
.card h1 {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
.updated {
|
||||||
|
margin: 0 0 2rem;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.card section {
|
||||||
|
margin: 0 0 1.75rem;
|
||||||
|
}
|
||||||
|
.card section h2 {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
.card p {
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.card ul {
|
||||||
|
margin: 0 0 0.6rem;
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.card li {
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
.card a {
|
||||||
|
color: #4338ca;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.card a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,6 +6,7 @@ import { setSessionCookie } from '$lib/server/session';
|
|||||||
import { issueAccess } from '$lib/server/member-otp';
|
import { issueAccess } from '$lib/server/member-otp';
|
||||||
import { slugify, handle, famUsername, handleOf } from '@shared/slugify';
|
import { slugify, handle, famUsername, handleOf } from '@shared/slugify';
|
||||||
import { applyAccessCode } from '$lib/server/access';
|
import { applyAccessCode } from '$lib/server/access';
|
||||||
|
import { sendWelcomeEmail, sendAccessUnlockedEmail } from '$lib/server/email';
|
||||||
import { createEmbeddedCheckoutSession, PLAN_IDS } from '$lib/server/stripe';
|
import { createEmbeddedCheckoutSession, PLAN_IDS } from '$lib/server/stripe';
|
||||||
import type { PlanId } from '$lib/server/stripe';
|
import type { PlanId } from '$lib/server/stripe';
|
||||||
|
|
||||||
@@ -70,6 +71,10 @@ export const actions = {
|
|||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
if (authResult?.token) setSessionCookie(event.cookies, authResult.token);
|
if (authResult?.token) setSessionCookie(event.cookies, authResult.token);
|
||||||
|
|
||||||
|
// Welcome email — a best-effort send. Failures must not block signup.
|
||||||
|
const dashboardUrl = `${event.url.origin}/${slug}`;
|
||||||
|
sendWelcomeEmail({ to: email, famName, dashboardUrl }).catch(() => {});
|
||||||
|
|
||||||
return { success: true, famSlug: slug, username: handleName };
|
return { success: true, famSlug: slug, username: handleName };
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -111,6 +116,27 @@ export const actions = {
|
|||||||
const result = await applyAccessCode(user.famId, code);
|
const result = await applyAccessCode(user.famId, code);
|
||||||
if (result.error) return fail(400, { error: result.error });
|
if (result.error) return fail(400, { error: result.error });
|
||||||
|
|
||||||
|
// Access code applied successfully — confirm by email (best-effort).
|
||||||
|
// Failure must never block the user continuing to the plan step.
|
||||||
|
if (result.ok) {
|
||||||
|
const fam = await pbAdmin
|
||||||
|
.getOne('fams', user.famId)
|
||||||
|
.catch(() => ({ name: '', slug: user.famId }));
|
||||||
|
const parents = await pbAdmin
|
||||||
|
.getList('users', `famId = '${user.famId}' && role = 'parent'`)
|
||||||
|
.catch(() => []);
|
||||||
|
const parentEmail = (parents[0] as { email?: string } | undefined | null)?.email;
|
||||||
|
if (parentEmail) {
|
||||||
|
sendAccessUnlockedEmail({
|
||||||
|
to: parentEmail,
|
||||||
|
famName: fam.name || '',
|
||||||
|
codeName: result.code?.name || code,
|
||||||
|
codeValue: code,
|
||||||
|
dashboardUrl: `${event.url.origin}/${fam.slug || user.famId}`
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { ok: true, ...result };
|
return { ok: true, ...result };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,10 @@
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" disabled={submitting}>Create my family</button>
|
<button type="submit" disabled={submitting}>Create my family</button>
|
||||||
|
<p class="privacy-note">
|
||||||
|
By signing up you agree to our <a href="/privacy">Privacy Policy</a>. We collect your email
|
||||||
|
only to create and log into your account, never sell or share your data.
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
<p class="alt">Already have a family? <a href="/login">Log in</a></p>
|
<p class="alt">Already have a family? <a href="/login">Log in</a></p>
|
||||||
{:else if step === 'child'}
|
{:else if step === 'child'}
|
||||||
@@ -381,6 +385,21 @@
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #059669;
|
color: #059669;
|
||||||
}
|
}
|
||||||
|
.privacy-note {
|
||||||
|
margin: 0.25rem 0 0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.privacy-note a {
|
||||||
|
color: #4338ca;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.privacy-note a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
.actions {
|
.actions {
|
||||||
margin-top: 1.25rem;
|
margin-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user