d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
235 lines
7.8 KiB
TypeScript
235 lines
7.8 KiB
TypeScript
'use client'
|
|
|
|
import { Suspense } from 'react'
|
|
import { Chip, cn, Loader } from '@sim/emcn'
|
|
import { getErrorMessage } from '@sim/utils/errors'
|
|
import { useSearchParams } from 'next/navigation'
|
|
import type { UnsubscribeType } from '@/lib/api/contracts/user'
|
|
import { AuthSubmitButton } from '@/app/(auth)/components'
|
|
import { AUTH_BUTTON_CLASS } from '@/app/(auth)/components/constants'
|
|
import { InviteLayout } from '@/app/invite/components'
|
|
import { useUnsubscribe, useUnsubscribeMutation } from '@/hooks/queries/unsubscribe'
|
|
|
|
function UnsubscribeContent() {
|
|
const searchParams = useSearchParams()
|
|
const email = searchParams.get('email')
|
|
const token = searchParams.get('token')
|
|
|
|
const hasParams = Boolean(email) && Boolean(token)
|
|
const query = useUnsubscribe(email ?? undefined, token ?? undefined)
|
|
const unsubscribe = useUnsubscribeMutation()
|
|
|
|
const data = query.data ?? null
|
|
const loading = hasParams && query.isLoading
|
|
const processing = unsubscribe.isPending
|
|
const unsubscribed = unsubscribe.isSuccess
|
|
const error = !hasParams
|
|
? 'Missing email or token in URL'
|
|
: query.isError
|
|
? getErrorMessage(query.error, 'Failed to validate unsubscribe link')
|
|
: unsubscribe.isError
|
|
? getErrorMessage(unsubscribe.error, 'Failed to process unsubscribe request')
|
|
: null
|
|
|
|
const handleUnsubscribe = (type: UnsubscribeType) => {
|
|
if (!email || !token) return
|
|
unsubscribe.mutate({ email, token, type })
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Loading
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>
|
|
Validating your unsubscribe link…
|
|
</p>
|
|
</div>
|
|
<div className={'mt-8 flex w-full items-center justify-center py-8'}>
|
|
<Loader className='size-8 text-[var(--text-muted)]' animate />
|
|
</div>
|
|
</InviteLayout>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Invalid Unsubscribe Link
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>{error}</p>
|
|
</div>
|
|
|
|
<div className={'mt-8 w-full max-w-[410px] space-y-3'}>
|
|
<AuthSubmitButton type='button' onClick={() => window.history.back()} loadingLabel=''>
|
|
Go Back
|
|
</AuthSubmitButton>
|
|
</div>
|
|
</InviteLayout>
|
|
)
|
|
}
|
|
|
|
if (data?.isTransactional) {
|
|
return (
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Important Account Emails
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>
|
|
Transactional emails like password resets, account confirmations, and security alerts
|
|
cannot be unsubscribed from as they contain essential information for your account.
|
|
</p>
|
|
</div>
|
|
|
|
<div className={'mt-8 w-full max-w-[410px] space-y-3'}>
|
|
<AuthSubmitButton type='button' onClick={() => window.close()} loadingLabel=''>
|
|
Close
|
|
</AuthSubmitButton>
|
|
</div>
|
|
</InviteLayout>
|
|
)
|
|
}
|
|
|
|
if (unsubscribed) {
|
|
return (
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Successfully Unsubscribed
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>
|
|
You have been unsubscribed from our emails. You will stop receiving emails within 48
|
|
hours.
|
|
</p>
|
|
</div>
|
|
|
|
<div className={'mt-8 w-full max-w-[410px] space-y-3'}>
|
|
<AuthSubmitButton type='button' onClick={() => window.close()} loadingLabel=''>
|
|
Close
|
|
</AuthSubmitButton>
|
|
</div>
|
|
</InviteLayout>
|
|
)
|
|
}
|
|
|
|
const isAlreadyUnsubscribedFromAll = data?.currentPreferences.unsubscribeAll
|
|
|
|
return (
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Email Preferences
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>
|
|
Choose which emails you'd like to stop receiving.
|
|
</p>
|
|
<p className={'mt-2 font-[380] text-[var(--text-muted)] text-sm'}>{data?.email}</p>
|
|
</div>
|
|
|
|
<div className={'mt-8 w-full max-w-[410px] space-y-3'}>
|
|
<AuthSubmitButton
|
|
type='button'
|
|
onClick={() => handleUnsubscribe('all')}
|
|
disabled={isAlreadyUnsubscribedFromAll}
|
|
loading={processing}
|
|
loadingLabel='Unsubscribing…'
|
|
>
|
|
{isAlreadyUnsubscribedFromAll
|
|
? 'Unsubscribed from All Emails'
|
|
: 'Unsubscribe from All Marketing Emails'}
|
|
</AuthSubmitButton>
|
|
|
|
<div className='py-2 text-center'>
|
|
<span className={'font-[380] text-[var(--text-muted)] text-sm'}>
|
|
or choose specific types
|
|
</span>
|
|
</div>
|
|
|
|
<Chip
|
|
fullWidth
|
|
flush
|
|
onClick={() => handleUnsubscribe('marketing')}
|
|
disabled={
|
|
processing ||
|
|
isAlreadyUnsubscribedFromAll ||
|
|
data?.currentPreferences.unsubscribeMarketing
|
|
}
|
|
className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
|
|
>
|
|
{data?.currentPreferences.unsubscribeMarketing
|
|
? 'Unsubscribed from Marketing'
|
|
: 'Unsubscribe from Marketing Emails'}
|
|
</Chip>
|
|
|
|
<Chip
|
|
fullWidth
|
|
flush
|
|
onClick={() => handleUnsubscribe('updates')}
|
|
disabled={
|
|
processing ||
|
|
isAlreadyUnsubscribedFromAll ||
|
|
data?.currentPreferences.unsubscribeUpdates
|
|
}
|
|
className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
|
|
>
|
|
{data?.currentPreferences.unsubscribeUpdates
|
|
? 'Unsubscribed from Updates'
|
|
: 'Unsubscribe from Product Updates'}
|
|
</Chip>
|
|
|
|
<Chip
|
|
fullWidth
|
|
flush
|
|
onClick={() => handleUnsubscribe('notifications')}
|
|
disabled={
|
|
processing ||
|
|
isAlreadyUnsubscribedFromAll ||
|
|
data?.currentPreferences.unsubscribeNotifications
|
|
}
|
|
className={cn(AUTH_BUTTON_CLASS, 'border border-[var(--border-1)]')}
|
|
>
|
|
{data?.currentPreferences.unsubscribeNotifications
|
|
? 'Unsubscribed from Notifications'
|
|
: 'Unsubscribe from Notifications'}
|
|
</Chip>
|
|
</div>
|
|
|
|
<div className={'mt-6 max-w-[410px] text-center'}>
|
|
<p className='font-[380] text-[var(--text-muted)] text-small'>
|
|
You'll continue receiving important account emails like password resets and security
|
|
alerts.
|
|
</p>
|
|
</div>
|
|
</InviteLayout>
|
|
)
|
|
}
|
|
|
|
export default function Unsubscribe() {
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<InviteLayout>
|
|
<div className='space-y-1 text-center'>
|
|
<h1 className={'font-medium text-[32px] text-[var(--text-primary)] tracking-tight'}>
|
|
Loading
|
|
</h1>
|
|
<p className={'font-[380] text-[var(--text-muted)] text-md'}>
|
|
Validating your unsubscribe link…
|
|
</p>
|
|
</div>
|
|
<div className={'mt-8 flex w-full items-center justify-center py-8'}>
|
|
<Loader className='size-8 text-[var(--text-muted)]' animate />
|
|
</div>
|
|
</InviteLayout>
|
|
}
|
|
>
|
|
<UnsubscribeContent />
|
|
</Suspense>
|
|
)
|
|
}
|