graph + mail dispatcher, test-email endpoint, notifications global
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
/**
|
||||
* Admin UI: a small "send test email" tool for the SiteIntegrations email tab.
|
||||
* Enter an address, click Send, and it POSTs to /api/ipal/test-email, which
|
||||
* sends through the currently-selected transport (SMTP or Graph). Shows the
|
||||
* result inline so you can confirm delivery — or read the exact error — without
|
||||
* leaving the panel.
|
||||
*
|
||||
* Assigned via a `ui` field's admin.components.Field.
|
||||
*/
|
||||
export const TestEmailButton = () => {
|
||||
const [to, setTo] = useState('')
|
||||
const [status, setStatus] = useState<
|
||||
| { kind: 'error'; msg: string }
|
||||
| { kind: 'idle' }
|
||||
| { kind: 'ok'; msg: string }
|
||||
| { kind: 'sending' }
|
||||
>({ kind: 'idle' })
|
||||
|
||||
const send = async () => {
|
||||
if (!to.trim()) {
|
||||
setStatus({ kind: 'error', msg: 'Enter a recipient address.' })
|
||||
return
|
||||
}
|
||||
setStatus({ kind: 'sending' })
|
||||
try {
|
||||
const res = await fetch('/api/ipal/test-email', {
|
||||
body: JSON.stringify({ to: to.trim() }),
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
method: 'POST',
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.ok) {
|
||||
setStatus({ kind: 'ok', msg: data.message ?? 'Test email sent.' })
|
||||
} else {
|
||||
setStatus({ kind: 'error', msg: data.error ?? 'Send failed.' })
|
||||
}
|
||||
} catch {
|
||||
setStatus({ kind: 'error', msg: 'Request failed. Is the server running?' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="field-type" style={{ marginTop: '1rem' }}>
|
||||
<label className="field-label">Send a test email</label>
|
||||
<p style={{ fontSize: '.85rem', marginTop: 0, opacity: 0.7 }}>
|
||||
Sends through the transport selected above. Save your changes first.
|
||||
</p>
|
||||
<div style={{ alignItems: 'center', display: 'flex', flexWrap: 'wrap', gap: '.5rem' }}>
|
||||
<input
|
||||
onChange={(e) => setTo(e.target.value)}
|
||||
placeholder="[email protected]"
|
||||
style={{ flex: 1, minWidth: '220px' }}
|
||||
type="email"
|
||||
value={to}
|
||||
/>
|
||||
<button
|
||||
className="btn btn--style-secondary"
|
||||
disabled={status.kind === 'sending'}
|
||||
onClick={send}
|
||||
style={{ whiteSpace: 'nowrap' }}
|
||||
type="button"
|
||||
>
|
||||
{status.kind === 'sending' ? 'Sending…' : 'Send test'}
|
||||
</button>
|
||||
</div>
|
||||
{status.kind === 'ok' && (
|
||||
<p style={{ color: 'var(--theme-success-500, green)', marginTop: '.5rem' }}>
|
||||
✓ {status.msg}
|
||||
</p>
|
||||
)}
|
||||
{status.kind === 'error' && (
|
||||
<p style={{ color: 'var(--theme-error-500, crimson)', marginTop: '.5rem' }}>
|
||||
✗ {status.msg}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TestEmailButton
|
||||
@@ -8,8 +8,30 @@ import type { Field } from 'payload'
|
||||
* panel while remaining inaccessible to anonymous API requests.
|
||||
*/
|
||||
export const smtpFields: Field[] = [
|
||||
{
|
||||
name: 'emailTransport',
|
||||
type: 'select',
|
||||
admin: {
|
||||
description:
|
||||
'How outbound email is sent. "Microsoft Graph" is only available when configured by the administrator (Intecion).',
|
||||
// The Graph option only makes sense when agency credentials exist in env.
|
||||
// We can't read process.env in the admin UI directly, so a client project
|
||||
// that hasn't set up Graph should filter this option via integrationsFields
|
||||
// override, or simply leave it on 'smtp'. The adapter enforces the real
|
||||
// availability at send time regardless of what's selected here.
|
||||
},
|
||||
defaultValue: 'smtp',
|
||||
options: [
|
||||
{ label: 'SMTP', value: 'smtp' },
|
||||
{ label: 'Microsoft Graph (Exchange)', value: 'graph' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
admin: {
|
||||
// Hide SMTP fields when Graph is selected — they're not used then.
|
||||
condition: (_, siblingData) => siblingData?.emailTransport !== 'graph',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'smtpHost',
|
||||
@@ -56,4 +78,13 @@ export const smtpFields: Field[] = [
|
||||
description: 'Default "from" display name.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'emailTest',
|
||||
type: 'ui',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@intecion/ipal-kit/client#TestEmailButton',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user