Add invite email templates and delivery workflow
This commit is contained in:
@@ -53,6 +53,7 @@ type OwnedInvite = {
|
||||
code: string
|
||||
label?: string | null
|
||||
description?: string | null
|
||||
recipient_email?: string | null
|
||||
max_uses?: number | null
|
||||
use_count: number
|
||||
remaining_uses?: number | null
|
||||
@@ -87,9 +88,12 @@ type OwnedInviteForm = {
|
||||
code: string
|
||||
label: string
|
||||
description: string
|
||||
recipient_email: string
|
||||
max_uses: string
|
||||
expires_at: string
|
||||
enabled: boolean
|
||||
send_email: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
type ProfileTab = 'overview' | 'activity' | 'invites' | 'security'
|
||||
@@ -98,9 +102,12 @@ const defaultOwnedInviteForm = (): OwnedInviteForm => ({
|
||||
code: '',
|
||||
label: '',
|
||||
description: '',
|
||||
recipient_email: '',
|
||||
max_uses: '',
|
||||
expires_at: '',
|
||||
enabled: true,
|
||||
send_email: false,
|
||||
message: '',
|
||||
})
|
||||
|
||||
const formatDate = (value?: string | null) => {
|
||||
@@ -250,9 +257,12 @@ export default function ProfilePage() {
|
||||
code: invite.code ?? '',
|
||||
label: invite.label ?? '',
|
||||
description: invite.description ?? '',
|
||||
recipient_email: invite.recipient_email ?? '',
|
||||
max_uses: typeof invite.max_uses === 'number' ? String(invite.max_uses) : '',
|
||||
expires_at: invite.expires_at ?? '',
|
||||
enabled: invite.enabled !== false,
|
||||
send_email: false,
|
||||
message: '',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -292,9 +302,12 @@ export default function ProfilePage() {
|
||||
code: inviteForm.code || null,
|
||||
label: inviteForm.label || null,
|
||||
description: inviteForm.description || null,
|
||||
recipient_email: inviteForm.recipient_email || null,
|
||||
max_uses: inviteForm.max_uses || null,
|
||||
expires_at: inviteForm.expires_at || null,
|
||||
enabled: inviteForm.enabled,
|
||||
send_email: inviteForm.send_email,
|
||||
message: inviteForm.message || null,
|
||||
}),
|
||||
}
|
||||
)
|
||||
@@ -307,7 +320,18 @@ export default function ProfilePage() {
|
||||
const text = await response.text()
|
||||
throw new Error(text || 'Invite save failed')
|
||||
}
|
||||
setInviteStatus(inviteEditingId == null ? 'Invite created.' : 'Invite updated.')
|
||||
const data = await response.json().catch(() => ({}))
|
||||
if (data?.email?.status === 'ok') {
|
||||
setInviteStatus(
|
||||
`${inviteEditingId == null ? 'Invite created' : 'Invite updated'} and email sent to ${data.email.recipient_email}.`
|
||||
)
|
||||
} else if (data?.email?.status === 'error') {
|
||||
setInviteStatus(
|
||||
`${inviteEditingId == null ? 'Invite created' : 'Invite updated'}, but email failed: ${data.email.detail}`
|
||||
)
|
||||
} else {
|
||||
setInviteStatus(inviteEditingId == null ? 'Invite created.' : 'Invite updated.')
|
||||
}
|
||||
resetInviteEditor()
|
||||
await reloadInvites()
|
||||
} catch (err) {
|
||||
@@ -603,6 +627,56 @@ export default function ProfilePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="invite-form-row">
|
||||
<div className="invite-form-row-label">
|
||||
<span>Delivery</span>
|
||||
<small>Save a recipient email and optionally send the invite immediately.</small>
|
||||
</div>
|
||||
<div className="invite-form-row-control invite-form-row-control--stacked">
|
||||
<label>
|
||||
<span>Recipient email</span>
|
||||
<input
|
||||
type="email"
|
||||
value={inviteForm.recipient_email}
|
||||
onChange={(event) =>
|
||||
setInviteForm((current) => ({
|
||||
...current,
|
||||
recipient_email: event.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="friend@example.com"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<span>Delivery note</span>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={inviteForm.message}
|
||||
onChange={(event) =>
|
||||
setInviteForm((current) => ({
|
||||
...current,
|
||||
message: event.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="Optional note to include in the email"
|
||||
/>
|
||||
</label>
|
||||
<label className="inline-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={inviteForm.send_email}
|
||||
onChange={(event) =>
|
||||
setInviteForm((current) => ({
|
||||
...current,
|
||||
send_email: event.target.checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
Send "You have been invited" email after saving
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="invite-form-row">
|
||||
<div className="invite-form-row-label">
|
||||
<span>Limits</span>
|
||||
@@ -700,6 +774,7 @@ export default function ProfilePage() {
|
||||
</p>
|
||||
)}
|
||||
<div className="admin-meta-row">
|
||||
<span>Recipient: {invite.recipient_email || 'Not set'}</span>
|
||||
<span>
|
||||
Uses: {invite.use_count}
|
||||
{typeof invite.max_uses === 'number' ? ` / ${invite.max_uses}` : ''}
|
||||
|
||||
Reference in New Issue
Block a user