diff --git a/src/features/directory/DirectorySetupCard.tsx b/src/features/directory/DirectorySetupCard.tsx index 2f93823..e6c85df 100644 --- a/src/features/directory/DirectorySetupCard.tsx +++ b/src/features/directory/DirectorySetupCard.tsx @@ -15,6 +15,35 @@ import { cn } from '@/lib/utils'; export const DIRECTORY_WIZARD_VIEW = 'Wizard/directory'; +/** "Guided or manual?" for connecting a directory. Manual opens Directories unless told otherwise. */ +export function DirectoryLaunchChoice({ + open, + onOpenChange, + onManual, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + onManual?: () => void; +}) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const section = useSchemaStore((s) => s.viewToSection['x:Directory']) ?? 'Settings'; + return ( + navigate(`/${section}/${DIRECTORY_WIZARD_VIEW}`)} + onManual={onManual ?? (() => navigate(`/${section}/x:Directory`))} + /> + ); +} + /** * inbuxa: at the top of Directories and Authentication, an invitation to * connect a directory with the guide (settings-reorg, first wave). Manual @@ -22,8 +51,6 @@ export const DIRECTORY_WIZARD_VIEW = 'Wizard/directory'; */ export function DirectorySetupCard({ className }: { className?: string }) { const { t } = useTranslation(); - const navigate = useNavigate(); - const section = useSchemaStore((s) => s.viewToSection['x:Directory']) ?? 'Settings'; const [open, setOpen] = useState(false); return (
setOpen(true)}> {t('dirCard.start', 'Set it up')} - navigate(`/${section}/${DIRECTORY_WIZARD_VIEW}`)} - onManual={() => undefined} - /> + undefined} />
); } diff --git a/src/features/limits/LimitsSummary.tsx b/src/features/limits/LimitsSummary.tsx index c3ea586..44cb8db 100644 --- a/src/features/limits/LimitsSummary.tsx +++ b/src/features/limits/LimitsSummary.tsx @@ -22,10 +22,37 @@ import { describe, type LimitObject, type RuleRecord } from './rules'; export const LIMITS_WIZARD_VIEW = 'Wizard/limits'; -export function LimitsSummary({ object }: { object: LimitObject }) { +/** "Guided or manual?" for limits. Manual opens the incoming limits unless told otherwise. */ +export function LimitsLaunchChoice({ + open, + onOpenChange, + onManual, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + onManual?: () => void; +}) { const { t } = useTranslation(); const navigate = useNavigate(); - const section = useSchemaStore((s) => s.viewToSection[object]) ?? 'Settings'; + const section = useSchemaStore((s) => s.viewToSection['x:MtaInboundThrottle']) ?? 'Settings'; + return ( + navigate(`/${section}/${LIMITS_WIZARD_VIEW}`)} + onManual={onManual ?? (() => navigate(`/${section}/x:MtaInboundThrottle`))} + /> + ); +} + +export function LimitsSummary({ object }: { object: LimitObject }) { + const { t } = useTranslation(); const [rules, setRules] = useState(null); const [open, setOpen] = useState(false); @@ -68,18 +95,7 @@ export function LimitsSummary({ object }: { object: LimitObject }) { ))} )} - navigate(`/${section}/${LIMITS_WIZARD_VIEW}`)} - onManual={() => undefined} - /> + undefined} /> ); } diff --git a/src/features/settings/SettingsOverviewPage.tsx b/src/features/settings/SettingsOverviewPage.tsx index b681143..afcf5d2 100644 --- a/src/features/settings/SettingsOverviewPage.tsx +++ b/src/features/settings/SettingsOverviewPage.tsx @@ -23,6 +23,8 @@ import { SETTINGS_CATEGORIES, SETTINGS_LAYOUT_NAME } from '@/lib/settingsLayout' import { resolveObject } from '@/lib/schemaResolver'; import { countChanged, singletonPages, type SettingsPageRef } from './changedFromDefault'; import { SENDING_WIZARD_VIEW, SendingLaunchChoice } from '@/features/sending/SendingSetupCard'; +import { DIRECTORY_WIZARD_VIEW, DirectoryLaunchChoice } from '@/features/directory/DirectorySetupCard'; +import { LIMITS_WIZARD_VIEW, LimitsLaunchChoice } from '@/features/limits/LimitsSummary'; import { CERTIFICATE_WIZARD_VIEW, CertificateLaunchChoice } from '@/features/certificates/CertificateSetupCard'; /** Calls per JMAP request; the protocol's own floor, so any server takes it. */ @@ -50,6 +52,31 @@ async function anyExist(objectName: string): Promise { /** Each guided setup registers here as it lands. */ const GUIDED_SETUPS: GuidedSetup[] = [ + { + id: 'limits', + title: 'Sending and receiving limits', + blurb: 'Rate limits that suit who uses this server, so one stolen password can’t send without end.', + to: { choice: (props) => }, + gate: LIMITS_WIZARD_VIEW, + isDone: async () => { + const [res] = await jmapRequest([ + [ + 'x:MtaInboundThrottle/get', + { accountId: getAccountId('x:MtaInboundThrottle'), ids: null, properties: ['id'] }, + '0', + ], + ]); + return ((res?.[1]?.list as unknown[] | undefined)?.length ?? 0) > 0; + }, + }, + { + id: 'directory', + title: 'Connect a sign-in directory', + blurb: + 'Let people sign in with Active Directory, LDAP or an OpenID Connect provider, tested before anything moves.', + to: { choice: (props) => }, + gate: DIRECTORY_WIZARD_VIEW, + }, { id: 'certificates', title: 'Certificates, automatically',