diff --git a/src/apps/officer-web/Screens/LandingPage/LandingPage.tsx b/src/apps/officer-web/Screens/LandingPage/LandingPage.tsx
deleted file mode 100644
index 7b3b5065..00000000
--- a/src/apps/officer-web/Screens/LandingPage/LandingPage.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { AuthLayout } from './Layout';
-import { Hero } from './components/Hero';
-
-export function LandingPage() {
- return (
-
-
-
- );
-}
diff --git a/src/apps/officer-web/Screens/LandingPage/Layout.tsx b/src/apps/officer-web/Screens/LandingPage/Layout.tsx
deleted file mode 100644
index 5024fd65..00000000
--- a/src/apps/officer-web/Screens/LandingPage/Layout.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import type { ReactNode } from 'react';
-import { useLocation, useNavigate } from 'react-router';
-import { useAuth } from 'hooks/useAuth';
-import { PixelGrid } from '../../../../workspaces/components/PixelGrid';
-import { DuckAvatar } from './components/DuckAvatar';
-import { SignupModal } from './components/SignupModal';
-import { LoginModal } from './components/LoginModal';
-import { ForgotPasswordModal } from './components/ForgotPasswordModal';
-import { VerifyModal } from './components/VerifyModal';
-import { ResetPasswordModal } from './components/ResetPasswordModal';
-
-type AuthLayoutProps = {
- children?: ReactNode;
-};
-
-export function AuthLayout({ children }: AuthLayoutProps) {
- const location = useLocation();
- const navigate = useNavigate();
- const { registrationOpen } = useAuth();
-
- const signupOpen = registrationOpen && location.pathname === '/auth/register';
- const loginOpen = location.pathname === '/auth/login';
- const forgotPasswordOpen = location.pathname === '/auth/forgot-password';
- const verifyOpen = location.pathname === '/auth/verify';
- const resetPasswordOpen = location.pathname === '/auth/reset-password';
-
- const closeModal = (open: boolean) => !open && navigate('/');
-
- return (
-
-
-
-
-
- {/* Background layer */}
-
-
- {/* Content layer - above duck and pixel grid */}
- {children}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/apps/officer-web/Screens/LandingPage/components/ForgotPasswordModal.tsx b/src/apps/officer-web/Screens/LandingPage/components/ForgotPasswordModal.tsx
deleted file mode 100644
index e8268c08..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/ForgotPasswordModal.tsx
+++ /dev/null
@@ -1,123 +0,0 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
-import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Button } from '@/components/ui/button';
-import { Link } from 'react-router';
-import { PaperDialog } from '@/components/Dialogs';
-import { useForm } from 'hooks/useForm';
-import { useAuth } from 'hooks/useAuth';
-
-type ForgotPasswordFormState = {
- email?: string;
-};
-
-const validateForm = (state: Partial) => {
- return !!state.email;
-};
-
-type ForgotPasswordModalProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-};
-
-export const ForgotPasswordModal = ({ open, onOpenChange }: ForgotPasswordModalProps) => {
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [sent, setSent] = useState(false);
- const { state, formRef, update, isValid } = useForm({}, validateForm);
- const { forgotPassword } = useAuth();
-
- const handleSubmit = async (ev: React.FormEvent) => {
- ev.preventDefault();
- if (!isValid || isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- await forgotPassword({ email: state.email! });
- setSent(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Failed to send recovery email. Please try again.');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleClose = (isOpen: boolean) => {
- if (!isOpen) {
- setSent(false);
- if (formRef.current) {
- update({ email: '' });
- }
- }
- onOpenChange(isOpen);
- };
-
- const isDisabled = !isValid || isSubmitting;
-
- return (
- {
- if (!sent) {
- const firstInput = (ev.currentTarget as HTMLElement | null)?.querySelector('input');
- firstInput?.focus();
- }
- }}
- >
- {sent ? (
-
-
Email Sent
-
Please check your email for further instructions.
-
-
- ) : (
- <>
-
- Forgot Password
-
- Enter your email and we'll send you a recovery link
-
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/apps/officer-web/Screens/LandingPage/components/Hero/Hero.tsx b/src/apps/officer-web/Screens/LandingPage/components/Hero/Hero.tsx
deleted file mode 100644
index c1832814..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/Hero/Hero.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { useState } from 'react';
-import { useNavigate } from 'react-router';
-import { toast } from 'sonner';
-import { useAuth } from 'hooks/useAuth';
-import { Button } from '@/components/ui/button';
-import { OfficerSvgLogo } from '../Logos';
-
-export function Hero() {
- const navigate = useNavigate();
- const { registrationOpen, signup } = useAuth();
- const [email, setEmail] = useState('');
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [done, setDone] = useState(false);
-
- const handleBootstrap = async () => {
- const trimmed = email.trim();
- if (!trimmed || isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- await signup({ email: trimmed });
- setDone(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Bootstrap failed. Please try again.');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- return (
- <>
-
-
- {registrationOpen ? (
-
-
- {done ? (
-
-
Check your email
-
- A verification link has been sent to {email}. Click it to activate your account.
-
-
- ) : (
- <>
-
Welcome, Admin
-
Enter your email to create your administrator account.
-
- setEmail(ev.target.value)}
- onKeyDown={(ev) => {
- if (ev.key === 'Enter') {
- ev.preventDefault();
- handleBootstrap();
- }
- }}
- placeholder="admin@example.com"
- className="flex-1 px-4 py-2.5 rounded-lg border-2 border-duck-dark/20 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:border-duck-teal/50"
- />
-
-
- >
- )}
-
-
- ) : (
-
-
-
- )}
- >
- );
-}
diff --git a/src/apps/officer-web/Screens/LandingPage/components/Hero/index.tsx b/src/apps/officer-web/Screens/LandingPage/components/Hero/index.tsx
deleted file mode 100644
index 729bca95..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/Hero/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from './Hero';
diff --git a/src/apps/officer-web/Screens/LandingPage/components/LoginModal.tsx b/src/apps/officer-web/Screens/LandingPage/components/LoginModal.tsx
deleted file mode 100644
index 1f2f036b..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/LoginModal.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
-import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Button } from '@/components/ui/button';
-import { Link } from 'react-router';
-import { PaperDialog } from '@/components/Dialogs';
-import { useForm } from 'hooks/useForm';
-import { useAuth } from 'hooks/useAuth';
-
-type LoginFormState = {
- email?: string;
- password?: string;
-};
-
-const validateForm = (state: Partial) => {
- const { email, password } = state;
- if (!email || !password) return false;
- return true;
-};
-
-type LoginModalProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-};
-
-export const LoginModal = ({ open, onOpenChange }: LoginModalProps) => {
- const [isSubmitting, setIsSubmitting] = useState(false);
- const { state, formRef, update, isValid } = useForm({}, validateForm);
- const { signin } = useAuth();
-
- const handleSubmit = async (ev: React.FormEvent) => {
- ev.preventDefault();
- if (!isValid || isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- await signin({ email: state.email!, password: state.password! });
- window.location.href = '/';
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Login failed. Please try again.');
- update({ ...state, password: '' });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleClose = (isOpen: boolean) => {
- if (!isOpen) {
- if (formRef.current) {
- update({ email: '', password: '' });
- }
- }
- onOpenChange(isOpen);
- };
-
- const isDisabled = !isValid || isSubmitting;
-
- return (
- {
- const firstInput = (ev.currentTarget as HTMLElement | null)?.querySelector('input');
- firstInput?.focus();
- }}
- >
-
- Welcome Back
- Sign in to your account
-
-
-
-
- );
-};
diff --git a/src/apps/officer-web/Screens/LandingPage/components/ResetPasswordModal.tsx b/src/apps/officer-web/Screens/LandingPage/components/ResetPasswordModal.tsx
deleted file mode 100644
index c8e1a533..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/ResetPasswordModal.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
-import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Button } from '@/components/ui/button';
-import { Link } from 'react-router';
-import { PaperDialog } from '@/components/Dialogs';
-import { useForm } from 'hooks/useForm';
-import { useAuth } from 'hooks/useAuth';
-
-type ResetPasswordFormState = {
- password?: string;
- confirmPassword?: string;
-};
-
-const validateForm = (state: Partial) => {
- const { password, confirmPassword } = state;
- if (!password || !confirmPassword) return false;
- if (password !== confirmPassword) return false;
- return true;
-};
-
-type ResetPasswordModalProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-};
-
-export const ResetPasswordModal = ({ open, onOpenChange }: ResetPasswordModalProps) => {
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [reset, setReset] = useState(false);
- const { state, formRef, update, isValid } = useForm({}, validateForm);
- const { resetPassword } = useAuth();
-
- const handleSubmit = async (ev: React.FormEvent) => {
- ev.preventDefault();
- if (!isValid || isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- const verificationCode = new URL(window.location.href).searchParams.get('verificationCode');
- if (!verificationCode) throw new Error('Invalid verification code');
- await resetPassword({ password: state.password!, verificationCode });
- setReset(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Password reset failed. Please try again.');
- update({ password: '', confirmPassword: '' });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleClose = (isOpen: boolean) => {
- if (!isOpen) {
- setReset(false);
- if (formRef.current) {
- update({ password: '', confirmPassword: '' });
- }
- }
- onOpenChange(isOpen);
- };
-
- const isDisabled = !isValid || isSubmitting;
-
- return (
- {
- if (!reset) {
- const firstInput = (ev.currentTarget as HTMLElement | null)?.querySelector('input');
- firstInput?.focus();
- }
- }}
- >
- {reset ? (
-
-
Password Reset
-
Your password has been reset. You can now sign in.
-
-
- ) : (
- <>
-
- Reset Password
- Enter your new password
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/apps/officer-web/Screens/LandingPage/components/SignupModal.tsx b/src/apps/officer-web/Screens/LandingPage/components/SignupModal.tsx
deleted file mode 100644
index 4273d4af..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/SignupModal.tsx
+++ /dev/null
@@ -1,164 +0,0 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
-import { useLocation, Link } from 'react-router';
-import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Button } from '@/components/ui/button';
-import { PaperDialog } from '@/components/Dialogs';
-import { useForm } from 'hooks/useForm';
-import { useAuth } from 'hooks/useAuth';
-
-type SignupFormState = {
- name?: string;
- email?: string;
- password?: string;
- confirmPassword?: string;
-};
-
-const validateForm = (state: Partial) => {
- const { name, email, password, confirmPassword } = state;
- if (!name || !email || !password || !confirmPassword) return false;
- if (password !== confirmPassword) return false;
- return true;
-};
-
-type SignupModalProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-};
-
-export const SignupModal = ({ open, onOpenChange }: SignupModalProps) => {
- const location = useLocation();
- const bootstrapEmail = (location.state as { email?: string } | null)?.email ?? '';
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [registered, setRegistered] = useState(false);
- const { state, formRef, update, isValid } = useForm({ email: bootstrapEmail }, validateForm);
- const { signup } = useAuth();
-
- const handleSubmit = async (ev: React.FormEvent) => {
- ev.preventDefault();
- if (!isValid || isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- await signup({ name: state.name, email: state.email, password: state.password });
- setRegistered(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Signup failed. Please try again.');
- update({ ...state, password: '', confirmPassword: '' });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleClose = (isOpen: boolean) => {
- if (!isOpen) {
- setRegistered(false);
- if (formRef.current) {
- update({ name: '', email: '', password: '', confirmPassword: '' });
- }
- }
- onOpenChange(isOpen);
- };
-
- const isDisabled = !isValid || isSubmitting;
-
- return (
- {
- if (!registered) {
- const firstInput = (ev.currentTarget as HTMLElement).querySelector('input');
- firstInput?.focus();
- }
- }}
- >
- {registered ? (
-
-
Registration Successful
-
Please check your email to verify your account.
-
-
- ) : (
- <>
-
- Create Account
- Create a new account to get started
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/apps/officer-web/Screens/LandingPage/components/VerifyModal.tsx b/src/apps/officer-web/Screens/LandingPage/components/VerifyModal.tsx
deleted file mode 100644
index 797146ca..00000000
--- a/src/apps/officer-web/Screens/LandingPage/components/VerifyModal.tsx
+++ /dev/null
@@ -1,243 +0,0 @@
-import { useState, useEffect } from 'react';
-import { useNavigate } from 'react-router';
-import { toast } from 'sonner';
-import { DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Button } from '@/components/ui/button';
-import { PaperDialog } from '@/components/Dialogs';
-import { useForm } from 'hooks/useForm';
-import { useAuth } from 'hooks/useAuth';
-import { useClient } from 'hooks/useClient';
-
-type VerifyFormState = {
- name?: string;
- password?: string;
- confirmPassword?: string;
-};
-
-const validateForm = (state: Partial) => {
- const { name, password, confirmPassword } = state;
- if (!name || !password || !confirmPassword) return false;
- if (password !== confirmPassword) return false;
- return true;
-};
-
-type VerifyModalProps = {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-};
-
-type TokenStatus = 'loading' | 'valid' | 'invalid';
-
-export const VerifyModal = ({ open, onOpenChange }: VerifyModalProps) => {
- const navigate = useNavigate();
- const client = useClient('/api/auth');
- const { verify } = useAuth();
- const { state, formRef, update, isValid } = useForm({}, validateForm);
-
- const [verificationCode] = useState(() => new URL(window.location.href).searchParams.get('verificationCode') ?? '');
- const [tokenStatus, setTokenStatus] = useState('loading');
- const [email, setEmail] = useState('');
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [verified, setVerified] = useState(false);
- const [resending, setResending] = useState(false);
- const [resent, setResent] = useState(false);
-
- // Validate token on mount and strip it from the URL
- useEffect(() => {
- if (!open || !verificationCode) {
- if (!verificationCode) setTokenStatus('invalid');
- return;
- }
-
- window.history.replaceState(null, '', '/auth/verify');
-
- client
- .post<{ email: string }>('/verify-token', { verificationCode })
- .then((data) => {
- setEmail(data.email);
- setTokenStatus('valid');
- })
- .catch(() => {
- // Try to decode email from expired token for resend
- try {
- const payload = JSON.parse(atob(verificationCode.split('.')[1]!));
- if (payload.email) setEmail(payload.email);
- } catch {
- // ignore
- }
- setTokenStatus('invalid');
- });
- }, [open]);
-
- // Redirect after successful verification
- useEffect(() => {
- if (!verified) return;
- const timer = setTimeout(() => navigate('/'), 3000);
- return () => clearTimeout(timer);
- }, [verified]);
-
- const handleSubmit = async (ev: React.FormEvent) => {
- ev.preventDefault();
- if (!isValid || isSubmitting || !verificationCode) return;
-
- setIsSubmitting(true);
- try {
- await verify({
- verificationCode,
- name: state.name,
- password: state.password,
- confirmPassword: state.confirmPassword,
- });
- setVerified(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Verification failed. Please try again.');
- update({ ...state, password: '', confirmPassword: '' });
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleResend = async () => {
- if (!email || resending) return;
- setResending(true);
- try {
- await client.post('/resend-verification', { email });
- setResent(true);
- } catch (ex) {
- const error = ex as { message?: string };
- toast.error(error.message || 'Failed to resend. Please try again.');
- } finally {
- setResending(false);
- }
- };
-
- // Loading state
- if (tokenStatus === 'loading') {
- return (
-
-
-
- Validating...
- Please wait
-
-
-
- );
- }
-
- // Invalid / expired token
- if (tokenStatus === 'invalid') {
- return (
-
-
-
Link Expired
-
- This verification link is no longer valid.{' '}
- {email ? 'Click below to receive a new one.' : 'Please request a new one.'}
-
- {resent ? (
-
- A new verification email has been sent to {email}.
-
- ) : (
- email && (
-
- )
- )}
-
-
- );
- }
-
- // Valid token — show form or success
- return (
- {
- const firstInput = (ev.currentTarget as HTMLElement).querySelector('input:not([disabled])');
- (firstInput as HTMLElement)?.focus();
- }}
- >
- {verified ? (
-
-
Account Verified
-
- Congratulations! Your account has been set up successfully. Redirecting...
-
-
- ) : (
- <>
-
- Set Up Your Account
- Complete your account details
-
-
-
- >
- )}
-
- );
-};
diff --git a/src/apps/officer-web/Screens/LandingPage/index.tsx b/src/apps/officer-web/Screens/LandingPage/index.tsx
deleted file mode 100644
index 13809481..00000000
--- a/src/apps/officer-web/Screens/LandingPage/index.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-export { LandingPage } from './LandingPage';
-export { AuthLayout } from './Layout';
diff --git a/src/workspaces/types/assets.d.ts b/src/workspaces/types/assets.d.ts
new file mode 100644
index 00000000..b288f318
--- /dev/null
+++ b/src/workspaces/types/assets.d.ts
@@ -0,0 +1,19 @@
+declare module '*.jpg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.jpeg' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.png' {
+ const src: string;
+ export default src;
+}
+
+declare module '*.svg' {
+ const src: string;
+ export default src;
+}
diff --git a/src/workspaces/types/globals.d.ts b/src/workspaces/types/globals.d.ts
index a643b763..60bb51dc 100644
--- a/src/workspaces/types/globals.d.ts
+++ b/src/workspaces/types/globals.d.ts
@@ -1,23 +1,3 @@
-declare module '*.jpg' {
- const src: string;
- export default src;
-}
-
-declare module '*.jpeg' {
- const src: string;
- export default src;
-}
-
-declare module '*.png' {
- const src: string;
- export default src;
-}
-
-declare module '*.svg' {
- const src: string;
- export default src;
-}
-
declare global {
type SelectOption = { value: number | string; label?: string; href?: string; hidden?: boolean };