feat: add terms, privacy pages, email notification preferences, and sign-in consent (#107)
- /terms — Terms of Service for editor + pascal.app platform - /privacy — Privacy Policy (data collection, third parties, user rights) - Email notification toggle in Settings (new column + migration) - Sign-in consent text linking to terms/privacy - Effective date: February 20, 2026 Co-authored-by: Anton Pascal <anton-pascal@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import Link from 'next/link'
|
||||
import { ArrowLeft, Pencil } from 'lucide-react'
|
||||
import { useRef, useState } from 'react'
|
||||
import { authClient } from '../lib/auth/client'
|
||||
import { updateUsername, updateProfile, uploadAvatar } from '../lib/auth/actions'
|
||||
import { updateUsername, updateProfile, uploadAvatar, updateEmailNotifications } from '../lib/auth/actions'
|
||||
|
||||
function GoogleIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
@@ -40,6 +40,7 @@ interface SettingsPageProps {
|
||||
currentUsername: string | null
|
||||
currentGithubUrl: string | null
|
||||
currentXUrl: string | null
|
||||
currentEmailNotifications: boolean
|
||||
connectedAccounts: { providerId: string; accountId: string }[]
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ export function SettingsPage({
|
||||
currentUsername,
|
||||
currentGithubUrl,
|
||||
currentXUrl,
|
||||
currentEmailNotifications,
|
||||
connectedAccounts,
|
||||
}: SettingsPageProps) {
|
||||
const [username, setUsername] = useState(currentUsername ?? '')
|
||||
@@ -67,6 +69,8 @@ export function SettingsPage({
|
||||
type: 'success' | 'error'
|
||||
text: string
|
||||
} | null>(null)
|
||||
const [emailNotifications, setEmailNotifications] = useState(currentEmailNotifications)
|
||||
const [isSavingNotifications, setIsSavingNotifications] = useState(false)
|
||||
|
||||
const isGoogleConnected = connectedAccounts.some((a) => a.providerId === 'google')
|
||||
const initials = currentUsername
|
||||
@@ -149,6 +153,14 @@ export function SettingsPage({
|
||||
(githubUrl.trim() || '') !== (currentGithubUrl ?? '') ||
|
||||
(xUrl.trim() || '') !== (currentXUrl ?? '')
|
||||
|
||||
const handleToggleEmailNotifications = async () => {
|
||||
const newValue = !emailNotifications
|
||||
setEmailNotifications(newValue)
|
||||
setIsSavingNotifications(true)
|
||||
await updateEmailNotifications(newValue)
|
||||
setIsSavingNotifications(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<header className="border-b border-border bg-background/95 backdrop-blur sticky top-0 z-10">
|
||||
@@ -305,6 +317,37 @@ export function SettingsPage({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Notifications Section */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Notifications</h2>
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-1">
|
||||
<div className="text-sm font-medium">Email notifications</div>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
Receive emails about new features and updates.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={emailNotifications}
|
||||
onClick={handleToggleEmailNotifications}
|
||||
disabled={isSavingNotifications}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 ${
|
||||
emailNotifications ? 'bg-primary' : 'bg-input'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform ${
|
||||
emailNotifications ? 'translate-x-5' : 'translate-x-0.5'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Social Links Section */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold">Social Links</h2>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { Mail, X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { authClient } from '../lib/auth/client'
|
||||
@@ -218,7 +219,15 @@ export function SignInDialog({ open, onOpenChange }: SignInDialogProps) {
|
||||
</form>
|
||||
|
||||
<p className="text-center text-muted-foreground text-xs">
|
||||
Sign in with Google or receive a magic link via email.
|
||||
By signing in, you agree to our{' '}
|
||||
<Link href="/terms" className="underline hover:text-foreground">
|
||||
Terms of Service
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link href="/privacy" className="underline hover:text-foreground">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -122,6 +122,7 @@ export async function getUserProfile(): Promise<{
|
||||
username: string | null
|
||||
githubUrl: string | null
|
||||
xUrl: string | null
|
||||
emailNotifications: boolean
|
||||
} | null> {
|
||||
const session = await getSession()
|
||||
if (!session?.user) return null
|
||||
@@ -131,6 +132,7 @@ export async function getUserProfile(): Promise<{
|
||||
username: schema.users.username,
|
||||
githubUrl: schema.users.githubUrl,
|
||||
xUrl: schema.users.xUrl,
|
||||
emailNotifications: schema.users.emailNotifications,
|
||||
})
|
||||
.from(schema.users)
|
||||
.where(eq(schema.users.id, session.user.id))
|
||||
@@ -278,3 +280,23 @@ export async function uploadAvatar(
|
||||
revalidatePath('/settings')
|
||||
return { success: true, imageUrl }
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user's email notification preference
|
||||
*/
|
||||
export async function updateEmailNotifications(
|
||||
enabled: boolean,
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const session = await getSession()
|
||||
if (!session?.user) {
|
||||
return { success: false, error: 'Not authenticated' }
|
||||
}
|
||||
|
||||
await db
|
||||
.update(schema.users)
|
||||
.set({ emailNotifications: enabled })
|
||||
.where(eq(schema.users.id, session.user.id))
|
||||
|
||||
revalidatePath('/settings')
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user