diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 991113ea..4364702c 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -6,7 +6,7 @@ import { useServerSettings } from 'state/useServerSettings'; import { useInitialData } from '@/state/useInitialData'; export function App() { - const { isLoading, isAuthenticated } = useAuth(); + const { isLoading, isAuthenticated, user } = useAuth(); const { onboardingComplete, plugins, isLoading: isServerSettingsLoading } = useServerSettings(); useInitialData(); @@ -37,8 +37,9 @@ export function App() { } /> } /> - } /> - } /> + : } /> + : } /> + : } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx b/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx index f6ea81ce..b226a60f 100644 --- a/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx +++ b/src/apps/officer-web/Screens/Authentication/VerifyScreen/VerifyScreen.tsx @@ -6,7 +6,7 @@ import { cn } from '@/lib/utils'; import { useVerifyScreen } from './useVerifyScreen'; export const VerifyScreen = () => { - const { formRef, isValid, tokenStatus, isSubmitting, handleSubmit } = useVerifyScreen(); + const { formRef, isValid, tokenStatus, flow, isSubmitting, handleSubmit } = useVerifyScreen(); const loading = tokenStatus === 'loading'; const invalid = tokenStatus === 'invalid'; const hideform = loading || invalid; @@ -30,8 +30,12 @@ export const VerifyScreen = () => {
-
Create Your Account
-
Set up your administrator profile
+
+ {flow === 'bootstrap' ? 'Create Your Account' : 'Accept Invitation'} +
+
+ {flow === 'bootstrap' ? 'Set up your administrator profile' : 'Set up your profile to get started'} +
diff --git a/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts b/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts index 8a9df7aa..2ab312e0 100644 --- a/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts +++ b/src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts @@ -9,18 +9,20 @@ export const useVerifyScreen = () => { const isMounted = useMounted(); const navigate = useNavigate(); const client = useClient('/api/auth'); - const { state, formRef, update, isValid } = useForm({ email: '' }, validateForm); + const { state, formRef, update } = useForm({ email: '' }); const [verificationCode] = useState(() => new URL(window.location.href).searchParams.get('verificationCode') ?? ''); const [tokenStatus, setTokenStatus] = useState('loading'); + const [flow, setFlow] = useState<'bootstrap' | 'invite' | 'verify'>('bootstrap'); const [isSubmitting, setIsSubmitting] = useState(false); const verifyToken = async () => { if (!verificationCode) return; try { - const data = await client.post<{ ok: boolean; email: string }>('/verify-token', { verificationCode }); + const data = await client.post<{ ok: boolean; email: string; flow: 'bootstrap' | 'invite' | 'verify' }>('/verify-token', { verificationCode }); if (data.ok) { setTokenStatus('valid'); + setFlow(data.flow); requestAnimationFrame(() => update({ email: data.email })); } else { setTokenStatus('invalid'); @@ -45,15 +47,25 @@ export const useVerifyScreen = () => { setIsSubmitting(true); try { - await client.post('/bootstrap', { - token: verificationCode, - name: state.name, - username: state.username, - password: state.password, - confirmPassword: state.confirmPassword, - }); + if (flow === 'bootstrap') { + await client.post('/bootstrap', { + token: verificationCode, + name: state.name, + username: state.username, + password: state.password, + confirmPassword: state.confirmPassword, + }); + } else { + await client.post('/verify', { + verificationCode, + name: state.name, + username: state.username, + password: state.password, + confirmPassword: state.confirmPassword, + }); + } toast.success('Account created. Please sign in.'); - navigate('/auth/login'); + navigate('/'); } catch (ex) { const error = ex as { message?: string }; toast.error(error.message || 'Failed to create account. Please try again.'); @@ -62,7 +74,9 @@ export const useVerifyScreen = () => { } }; - return { formRef, state, isValid, tokenStatus, isSubmitting, handleSubmit }; + const isValid = flow === 'bootstrap' ? validateBootstrap(state) : validateInvite(state); + + return { formRef, state, isValid, tokenStatus, flow, isSubmitting, handleSubmit }; }; type VerifyFormState = { @@ -75,7 +89,14 @@ type VerifyFormState = { type TokenStatus = 'loading' | 'valid' | 'invalid'; -const validateForm = (state: Partial) => { +const validateBootstrap = (state: Partial) => { + const { name, username, password, confirmPassword } = state; + if (!name || !username || !password || !confirmPassword) return false; + if (password !== confirmPassword) return false; + return true; +}; + +const validateInvite = (state: Partial) => { const { name, username, password, confirmPassword } = state; if (!name || !username || !password || !confirmPassword) return false; if (password !== confirmPassword) return false; diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx index c08e011d..9588006a 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx @@ -1,7 +1,7 @@ import { Link } from 'react-router'; import * as Dropdown from '@/components/ui/dropdown-menu'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { User, LogOut, Settings, Package, Sun, Moon } from 'lucide-react'; +import { User, Users, LogOut, Settings, Package, Sun, Moon } from 'lucide-react'; import { useAuth } from 'hooks/useAuth'; import { useTranslation } from '@/lib/i18n'; import { useColorMode } from '@/components/ui/ThemeProvider'; @@ -16,6 +16,8 @@ export function UserMenu() { const { settings, saveSettings } = useSettings(); if (isLoading) return null; + const isAdmin = user?.role !== 'Member'; + const toggleColorMode = () => { const next = colorMode === 'dark' ? 'light' : 'dark'; setColorMode(next); @@ -41,18 +43,30 @@ export function UserMenu() { {t('header.userMenu.profile')} - - - - {t('header.userMenu.systemSettings')} - - - - - - {t('header.userMenu.resources')} - - + {isAdmin && ( + <> + + + + {t('header.userMenu.systemSettings')} + + + + + + {t('header.userMenu.resources')} + + + + )} + {user?.role === 'Super Admin' && ( + + + + Users + + + )} {colorMode === 'dark' ? ( diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx index e5b1f3b7..5c101203 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/UserData.tsx @@ -102,6 +102,16 @@ export const UserData = () => { /> + +