ui phase 8
This commit is contained in:
@@ -1,81 +1,99 @@
|
||||
'use client';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Paper, Stack, Typography } from '@mui/material';
|
||||
import { AppButton, AppIcon } from '@/components';
|
||||
import { ROUTES } from '@/constants';
|
||||
import { useVerificationStatus } from '@/services/verification';
|
||||
import { isApproved } from '@/services/verification/types';
|
||||
import { Skeleton, Stack, Typography } from '@mui/material';
|
||||
import { AccentCard, AppButton, AppIcon } from '@/components';
|
||||
import { useActivationChecklist } from '@/components/ActivationChecklist';
|
||||
import { useSetAcceptingBookings } from '@/services/profiles';
|
||||
|
||||
/**
|
||||
* The go-live gate for the nurse's services (the f4 publish stub, wired to verification here). A nurse
|
||||
* is **not bookable and cannot publish until verified**: when the aggregate isn't `approved` the publish
|
||||
* CTA is **disabled** with a blocked-until-verified explanation that links to the B3 checklist; once
|
||||
* approved it enables. Mirrors the server's guarded `is_verified` flip — the UI never implies a nurse is
|
||||
* live before verification completes. Reads the shared `VerificationStatus` query (cached across the app).
|
||||
* The go-live gate for the nurse's services — **the real switch**, not a snackbar. Reads the same
|
||||
* `useActivationChecklist` state `ActivationChecklist` renders (one source of truth, no duplicated
|
||||
* search-visibility logic): unmet conditions render guidance naming exactly what's missing; met but
|
||||
* paused renders the real `set_accepting_bookings` CTA; live renders the on state + a pause action.
|
||||
* Success copy fires only after the mutation actually succeeds — this replaces the old
|
||||
* `enqueueSnackbar('published')` no-op.
|
||||
*/
|
||||
const PublishGate: FunctionComponent = () => {
|
||||
const t = useTranslations('verification');
|
||||
const locale = useLocale();
|
||||
const tActivation = useTranslations('activation');
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { data: status, isLoading } = useVerificationStatus();
|
||||
const state = useActivationChecklist();
|
||||
const setAccepting = useSetAcceptingBookings();
|
||||
|
||||
if (isLoading) return null;
|
||||
const approved = isApproved(status);
|
||||
if (state.isLoading) return <Skeleton variant="rounded" height={96} sx={{ borderRadius: 2 }} />;
|
||||
if (state.isError) return null; // ActivationChecklist (mounted alongside) already surfaces the error + retry.
|
||||
|
||||
const toggle = (accepting: boolean) => {
|
||||
setAccepting.mutate(accepting, {
|
||||
onSuccess: () =>
|
||||
enqueueSnackbar(accepting ? t('publish_accepting_success') : t('publish_paused_success'), {
|
||||
variant: 'success',
|
||||
}),
|
||||
onError: () => enqueueSnackbar(t('publish_toggle_error'), { variant: 'error' }),
|
||||
});
|
||||
};
|
||||
|
||||
const live = state.isSearchVisible && state.isAcceptingBookings;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
borderInlineStartWidth: 4,
|
||||
borderInlineStartColor: approved ? 'var(--bal-success)' : 'var(--bal-warning)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 1,
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" sx={{ gap: 1.5, alignItems: 'flex-start' }}>
|
||||
<AppIcon
|
||||
icon={approved ? 'verified' : 'warning'}
|
||||
size={24}
|
||||
color={approved ? 'var(--bal-success)' : 'var(--bal-warning)'}
|
||||
/>
|
||||
<Stack sx={{ gap: 0.5, flexGrow: 1 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
|
||||
{approved ? t('publish_ready_title') : t('publish_blocked_title')}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||
{approved ? t('publish_ready_body') : t('publish_blocked_body')}
|
||||
</Typography>
|
||||
<AccentCard tone={live ? 'success' : state.isSearchVisible ? 'primary' : 'warning'} padding="md">
|
||||
<Stack sx={{ gap: 1 }}>
|
||||
<Stack direction="row" sx={{ gap: 1.5, alignItems: 'flex-start' }}>
|
||||
<AppIcon
|
||||
icon={live ? 'verified' : state.isSearchVisible ? 'publish' : 'warning'}
|
||||
size={24}
|
||||
color={live ? 'var(--bal-success)' : state.isSearchVisible ? 'var(--bal-primary)' : 'var(--bal-warning)'}
|
||||
/>
|
||||
<Stack sx={{ gap: 0.5, flexGrow: 1 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
|
||||
{live
|
||||
? t('publish_live_title')
|
||||
: state.isSearchVisible
|
||||
? t('publish_ready_title')
|
||||
: t('publish_blocked_title')}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||
{live
|
||||
? t('publish_live_body')
|
||||
: state.isSearchVisible
|
||||
? t('publish_ready_body')
|
||||
: t('publish_unmet_intro', {
|
||||
items: state.searchRows
|
||||
.filter((row) => !row.passed)
|
||||
.map((row) => tActivation(row.labelKey))
|
||||
.join('، '),
|
||||
})}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||
{live ? (
|
||||
<AppButton
|
||||
color="inherit"
|
||||
variant="outlined"
|
||||
startIcon="pending"
|
||||
onClick={() => toggle(false)}
|
||||
disabled={setAccepting.isPending}
|
||||
>
|
||||
{setAccepting.isPending ? t('publish_toggling') : t('publish_pause_accepting')}
|
||||
</AppButton>
|
||||
) : state.isSearchVisible ? (
|
||||
<AppButton
|
||||
color="primary"
|
||||
variant="contained"
|
||||
startIcon="publish"
|
||||
onClick={() => toggle(true)}
|
||||
disabled={setAccepting.isPending}
|
||||
>
|
||||
{setAccepting.isPending ? t('publish_toggling') : t('publish_start_accepting')}
|
||||
</AppButton>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap' }}>
|
||||
<AppButton
|
||||
color="primary"
|
||||
variant="contained"
|
||||
startIcon="publish"
|
||||
disabled={!approved}
|
||||
onClick={() => enqueueSnackbar(t('publish_done'), { variant: 'success' })}
|
||||
>
|
||||
{t('publish_cta')}
|
||||
</AppButton>
|
||||
{!approved ? (
|
||||
<AppButton
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
to={`/${locale}${ROUTES.NURSE_VERIFICATION}`}
|
||||
>
|
||||
{t('publish_complete_verification')}
|
||||
</AppButton>
|
||||
) : null}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</AccentCard>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user