@mettlecast/domain-cli 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +9 -1
- package/dist/commands/add-page.js +39 -126
- package/dist/commands/build-ui/component-scanner.d.ts +12 -0
- package/dist/commands/build-ui/component-scanner.js +50 -0
- package/dist/commands/build-ui/page-indexer.d.ts +13 -0
- package/dist/commands/build-ui/page-indexer.js +107 -0
- package/dist/commands/build-ui.d.ts +3 -0
- package/dist/commands/build-ui.js +23 -0
- package/dist/templates/api-skeleton.js +1 -1
- package/dist/templates/events-skeleton.js +1 -1
- package/dist/templates/subscriber-skeleton.js +1 -1
- package/package.json +1 -1
- package/src/cli.ts +10 -1
- package/src/commands/add-page.ts +40 -156
- package/src/commands/build-ui/component-scanner.ts +70 -0
- package/src/commands/build-ui/page-indexer.ts +134 -0
- package/src/commands/build-ui.ts +29 -0
- package/src/templates/api-skeleton.ts +1 -1
- package/src/templates/events-skeleton.ts +1 -1
- package/src/templates/subscriber-skeleton.ts +1 -1
- package/dist/templates/dashboard-pages/index.d.ts +0 -101
- package/dist/templates/dashboard-pages/index.js +0 -22
- package/src/templates/dashboard-pages/account/api-keys.tsx +0 -107
- package/src/templates/dashboard-pages/account/audit-log.tsx +0 -60
- package/src/templates/dashboard-pages/account/index.ts +0 -5
- package/src/templates/dashboard-pages/account/members.tsx +0 -107
- package/src/templates/dashboard-pages/account/profile.tsx +0 -53
- package/src/templates/dashboard-pages/account/workspace-settings.tsx +0 -64
- package/src/templates/dashboard-pages/auth/accept-invitation.tsx +0 -62
- package/src/templates/dashboard-pages/auth/choose-org.tsx +0 -66
- package/src/templates/dashboard-pages/auth/forgot-password.tsx +0 -69
- package/src/templates/dashboard-pages/auth/index.ts +0 -10
- package/src/templates/dashboard-pages/auth/login.tsx +0 -75
- package/src/templates/dashboard-pages/auth/mfa-setup.tsx +0 -56
- package/src/templates/dashboard-pages/auth/mfa-verify.tsx +0 -49
- package/src/templates/dashboard-pages/auth/reset-password.tsx +0 -64
- package/src/templates/dashboard-pages/auth/sign-out.tsx +0 -20
- package/src/templates/dashboard-pages/auth/signup.tsx +0 -71
- package/src/templates/dashboard-pages/auth/verify-email.tsx +0 -53
- package/src/templates/dashboard-pages/index.ts +0 -22
- package/src/templates/dashboard-pages/shell/dashboard-home.tsx +0 -41
- package/src/templates/dashboard-pages/shell/forbidden.tsx +0 -46
- package/src/templates/dashboard-pages/shell/index.ts +0 -5
- package/src/templates/dashboard-pages/shell/maintenance.tsx +0 -27
- package/src/templates/dashboard-pages/shell/not-found.tsx +0 -34
- package/src/templates/dashboard-pages/shell/server-error.tsx +0 -44
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
-
import { signIn } from '@mettlecast/dashboard-shell';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function LoginPage() {
|
|
7
|
-
const navigate = useNavigate();
|
|
8
|
-
const [email, setEmail] = useState('');
|
|
9
|
-
const [password, setPassword] = useState('');
|
|
10
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
-
const [loading, setLoading] = useState(false);
|
|
12
|
-
|
|
13
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
14
|
-
e.preventDefault();
|
|
15
|
-
setLoading(true);
|
|
16
|
-
setError(null);
|
|
17
|
-
try {
|
|
18
|
-
await signIn(email, password);
|
|
19
|
-
navigate('/');
|
|
20
|
-
} catch (err) {
|
|
21
|
-
setError(err instanceof Error ? err.message : 'Sign in failed');
|
|
22
|
-
} finally {
|
|
23
|
-
setLoading(false);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
29
|
-
<div className="w-full max-w-md space-y-6">
|
|
30
|
-
<div>
|
|
31
|
-
<h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
|
|
32
|
-
<p className="text-sm text-muted-foreground mt-1">Enter your credentials to continue</p>
|
|
33
|
-
</div>
|
|
34
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
35
|
-
<TibFormField name="email" label="Email">
|
|
36
|
-
<input
|
|
37
|
-
id="email"
|
|
38
|
-
type="email"
|
|
39
|
-
value={email}
|
|
40
|
-
onChange={e => setEmail(e.target.value)}
|
|
41
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
42
|
-
required
|
|
43
|
-
autoComplete="email"
|
|
44
|
-
autoFocus
|
|
45
|
-
/>
|
|
46
|
-
</TibFormField>
|
|
47
|
-
<TibFormField name="password" label="Password">
|
|
48
|
-
<input
|
|
49
|
-
id="password"
|
|
50
|
-
type="password"
|
|
51
|
-
value={password}
|
|
52
|
-
onChange={e => setPassword(e.target.value)}
|
|
53
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
54
|
-
required
|
|
55
|
-
autoComplete="current-password"
|
|
56
|
-
/>
|
|
57
|
-
</TibFormField>
|
|
58
|
-
<div className="text-right">
|
|
59
|
-
<Link to="/forgot-password" className="text-sm text-primary hover:underline underline-offset-4">
|
|
60
|
-
Forgot password?
|
|
61
|
-
</Link>
|
|
62
|
-
</div>
|
|
63
|
-
<TibFormError error={error} />
|
|
64
|
-
<TibSubmitButton loading={loading} className="w-full">Sign in</TibSubmitButton>
|
|
65
|
-
</form>
|
|
66
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
67
|
-
No account?{' '}
|
|
68
|
-
<Link to="/signup" className="text-primary hover:underline underline-offset-4">Create one</Link>
|
|
69
|
-
</p>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export default LoginPage;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '@mettlecast/sdk';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function MfaSetupPage() {
|
|
7
|
-
const tib = useApi();
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
const [code, setCode] = useState('');
|
|
10
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
-
const [loading, setLoading] = useState(false);
|
|
12
|
-
const totpUri = (tib as any)?.auth?.getTotpUri?.() ?? '';
|
|
13
|
-
|
|
14
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
15
|
-
e.preventDefault();
|
|
16
|
-
setLoading(true);
|
|
17
|
-
setError(null);
|
|
18
|
-
try {
|
|
19
|
-
await (tib as any)?.auth?.verifyTotpSetup({ code });
|
|
20
|
-
navigate('/');
|
|
21
|
-
} catch (err) {
|
|
22
|
-
setError(err instanceof Error ? err.message : 'MFA setup failed');
|
|
23
|
-
} finally {
|
|
24
|
-
setLoading(false);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
30
|
-
<div className="w-full max-w-md space-y-6">
|
|
31
|
-
<div>
|
|
32
|
-
<h1 className="text-2xl font-semibold tracking-tight">Set up two-factor authentication</h1>
|
|
33
|
-
<p className="text-sm text-muted-foreground mt-1">Scan the QR code with your authenticator app</p>
|
|
34
|
-
</div>
|
|
35
|
-
{totpUri && (
|
|
36
|
-
<div className="flex justify-center">
|
|
37
|
-
<img src={`https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(totpUri)}`}
|
|
38
|
-
alt="TOTP QR code" className="rounded-md border border-border" width={180} height={180} />
|
|
39
|
-
</div>
|
|
40
|
-
)}
|
|
41
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
42
|
-
<TibFormField name="code" label="6-digit code">
|
|
43
|
-
<input id="code" type="text" inputMode="numeric" pattern="[0-9]{6}" value={code}
|
|
44
|
-
onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
45
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm tracking-widest text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
46
|
-
required autoComplete="one-time-code" autoFocus maxLength={6} />
|
|
47
|
-
</TibFormField>
|
|
48
|
-
<TibFormError error={error} />
|
|
49
|
-
<TibSubmitButton loading={loading} className="w-full">Enable MFA</TibSubmitButton>
|
|
50
|
-
</form>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default MfaSetupPage;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '@mettlecast/sdk';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function MfaVerifyPage() {
|
|
7
|
-
const tib = useApi();
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
const [code, setCode] = useState('');
|
|
10
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
-
const [loading, setLoading] = useState(false);
|
|
12
|
-
|
|
13
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
14
|
-
e.preventDefault();
|
|
15
|
-
setLoading(true);
|
|
16
|
-
setError(null);
|
|
17
|
-
try {
|
|
18
|
-
await (tib as any)?.auth?.verifyMfa({ code });
|
|
19
|
-
navigate('/');
|
|
20
|
-
} catch (err) {
|
|
21
|
-
setError(err instanceof Error ? err.message : 'Verification failed');
|
|
22
|
-
} finally {
|
|
23
|
-
setLoading(false);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
29
|
-
<div className="w-full max-w-md space-y-6">
|
|
30
|
-
<div>
|
|
31
|
-
<h1 className="text-2xl font-semibold tracking-tight">Two-factor authentication</h1>
|
|
32
|
-
<p className="text-sm text-muted-foreground mt-1">Enter the code from your authenticator app</p>
|
|
33
|
-
</div>
|
|
34
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
35
|
-
<TibFormField name="code" label="Authentication code">
|
|
36
|
-
<input id="code" type="text" inputMode="numeric" pattern="[0-9]{6}" value={code}
|
|
37
|
-
onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
38
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm tracking-widest text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
39
|
-
required autoComplete="one-time-code" autoFocus maxLength={6} />
|
|
40
|
-
</TibFormField>
|
|
41
|
-
<TibFormError error={error} />
|
|
42
|
-
<TibSubmitButton loading={loading} className="w-full">Verify</TibSubmitButton>
|
|
43
|
-
</form>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default MfaVerifyPage;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '@mettlecast/sdk';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function ResetPasswordPage() {
|
|
7
|
-
const tib = useApi();
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
const [searchParams] = useSearchParams();
|
|
10
|
-
const email = searchParams.get('email') ?? '';
|
|
11
|
-
const [code, setCode] = useState('');
|
|
12
|
-
const [password, setPassword] = useState('');
|
|
13
|
-
const [confirm, setConfirm] = useState('');
|
|
14
|
-
const [error, setError] = useState<string | null>(null);
|
|
15
|
-
const [loading, setLoading] = useState(false);
|
|
16
|
-
|
|
17
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
18
|
-
e.preventDefault();
|
|
19
|
-
if (password !== confirm) { setError('Passwords do not match'); return; }
|
|
20
|
-
setLoading(true);
|
|
21
|
-
setError(null);
|
|
22
|
-
try {
|
|
23
|
-
await (tib as any)?.auth?.confirmForgotPassword({ email, code, newPassword: password });
|
|
24
|
-
navigate('/login');
|
|
25
|
-
} catch (err) {
|
|
26
|
-
setError(err instanceof Error ? err.message : 'Reset failed');
|
|
27
|
-
} finally {
|
|
28
|
-
setLoading(false);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
34
|
-
<div className="w-full max-w-md space-y-6">
|
|
35
|
-
<div>
|
|
36
|
-
<h1 className="text-2xl font-semibold tracking-tight">Set new password</h1>
|
|
37
|
-
<p className="text-sm text-muted-foreground mt-1">Enter the code from your email and a new password</p>
|
|
38
|
-
</div>
|
|
39
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
40
|
-
<TibFormField name="code" label="Reset code">
|
|
41
|
-
<input id="code" type="text" inputMode="numeric" value={code}
|
|
42
|
-
onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
43
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm tracking-widest text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
44
|
-
required autoComplete="one-time-code" autoFocus maxLength={6} />
|
|
45
|
-
</TibFormField>
|
|
46
|
-
<TibFormField name="password" label="New password">
|
|
47
|
-
<input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)}
|
|
48
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
49
|
-
required autoComplete="new-password" minLength={8} />
|
|
50
|
-
</TibFormField>
|
|
51
|
-
<TibFormField name="confirm" label="Confirm password">
|
|
52
|
-
<input id="confirm" type="password" value={confirm} onChange={e => setConfirm(e.target.value)}
|
|
53
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
54
|
-
required autoComplete="new-password" />
|
|
55
|
-
</TibFormField>
|
|
56
|
-
<TibFormError error={error} />
|
|
57
|
-
<TibSubmitButton loading={loading} className="w-full">Reset password</TibSubmitButton>
|
|
58
|
-
</form>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export default ResetPasswordPage;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { useNavigate } from 'react-router-dom';
|
|
3
|
-
import { signOut } from '@mettlecast/dashboard-shell';
|
|
4
|
-
|
|
5
|
-
export function SignOutPage() {
|
|
6
|
-
const navigate = useNavigate();
|
|
7
|
-
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
signOut();
|
|
10
|
-
navigate('/login', { replace: true });
|
|
11
|
-
}, [navigate]);
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
15
|
-
<p className="text-sm text-muted-foreground">Signing out…</p>
|
|
16
|
-
</div>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default SignOutPage;
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate, Link } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '@mettlecast/sdk';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function SignupPage() {
|
|
7
|
-
const tib = useApi();
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
const [name, setName] = useState('');
|
|
10
|
-
const [email, setEmail] = useState('');
|
|
11
|
-
const [password, setPassword] = useState('');
|
|
12
|
-
const [orgName, setOrgName] = useState('');
|
|
13
|
-
const [error, setError] = useState<string | null>(null);
|
|
14
|
-
const [loading, setLoading] = useState(false);
|
|
15
|
-
|
|
16
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
17
|
-
e.preventDefault();
|
|
18
|
-
setLoading(true);
|
|
19
|
-
setError(null);
|
|
20
|
-
try {
|
|
21
|
-
// tib.auth.signUp is implemented by the auth domain
|
|
22
|
-
await (tib as any)?.auth?.signUp({ name, email, password, orgName });
|
|
23
|
-
navigate('/verify-email', { state: { email } });
|
|
24
|
-
} catch (err) {
|
|
25
|
-
setError(err instanceof Error ? err.message : 'Sign up failed');
|
|
26
|
-
} finally {
|
|
27
|
-
setLoading(false);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
33
|
-
<div className="w-full max-w-md space-y-6">
|
|
34
|
-
<div>
|
|
35
|
-
<h1 className="text-2xl font-semibold tracking-tight">Create account</h1>
|
|
36
|
-
<p className="text-sm text-muted-foreground mt-1">Set up your workspace</p>
|
|
37
|
-
</div>
|
|
38
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
39
|
-
<TibFormField name="name" label="Full name">
|
|
40
|
-
<input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
|
|
41
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
42
|
-
required autoComplete="name" autoFocus />
|
|
43
|
-
</TibFormField>
|
|
44
|
-
<TibFormField name="orgName" label="Organisation name">
|
|
45
|
-
<input id="orgName" type="text" value={orgName} onChange={e => setOrgName(e.target.value)}
|
|
46
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
47
|
-
required autoComplete="organization" />
|
|
48
|
-
</TibFormField>
|
|
49
|
-
<TibFormField name="email" label="Work email">
|
|
50
|
-
<input id="email" type="email" value={email} onChange={e => setEmail(e.target.value)}
|
|
51
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
52
|
-
required autoComplete="email" />
|
|
53
|
-
</TibFormField>
|
|
54
|
-
<TibFormField name="password" label="Password">
|
|
55
|
-
<input id="password" type="password" value={password} onChange={e => setPassword(e.target.value)}
|
|
56
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
57
|
-
required autoComplete="new-password" minLength={8} />
|
|
58
|
-
</TibFormField>
|
|
59
|
-
<TibFormError error={error} />
|
|
60
|
-
<TibSubmitButton loading={loading} className="w-full">Create account</TibSubmitButton>
|
|
61
|
-
</form>
|
|
62
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
63
|
-
Already have an account?{' '}
|
|
64
|
-
<Link to="/login" className="text-primary hover:underline underline-offset-4">Sign in</Link>
|
|
65
|
-
</p>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export default SignupPage;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useNavigate, useLocation } from 'react-router-dom';
|
|
3
|
-
import { useApi } from '@mettlecast/sdk';
|
|
4
|
-
import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
|
|
5
|
-
|
|
6
|
-
export function VerifyEmailPage() {
|
|
7
|
-
const tib = useApi();
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
const location = useLocation();
|
|
10
|
-
const email = (location.state as { email?: string })?.email ?? '';
|
|
11
|
-
const [code, setCode] = useState('');
|
|
12
|
-
const [error, setError] = useState<string | null>(null);
|
|
13
|
-
const [loading, setLoading] = useState(false);
|
|
14
|
-
|
|
15
|
-
async function handleSubmit(e: React.FormEvent) {
|
|
16
|
-
e.preventDefault();
|
|
17
|
-
setLoading(true);
|
|
18
|
-
setError(null);
|
|
19
|
-
try {
|
|
20
|
-
await (tib as any)?.auth?.confirmSignUp({ email, code });
|
|
21
|
-
navigate('/login');
|
|
22
|
-
} catch (err) {
|
|
23
|
-
setError(err instanceof Error ? err.message : 'Verification failed');
|
|
24
|
-
} finally {
|
|
25
|
-
setLoading(false);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
31
|
-
<div className="w-full max-w-md space-y-6">
|
|
32
|
-
<div>
|
|
33
|
-
<h1 className="text-2xl font-semibold tracking-tight">Verify your email</h1>
|
|
34
|
-
<p className="text-sm text-muted-foreground mt-1">
|
|
35
|
-
We sent a 6-digit code to <strong>{email || 'your email'}</strong>
|
|
36
|
-
</p>
|
|
37
|
-
</div>
|
|
38
|
-
<form onSubmit={handleSubmit} className="space-y-4" noValidate>
|
|
39
|
-
<TibFormField name="code" label="Verification code">
|
|
40
|
-
<input id="code" type="text" inputMode="numeric" pattern="[0-9]{6}" value={code}
|
|
41
|
-
onChange={e => setCode(e.target.value.replace(/\D/g, '').slice(0, 6))}
|
|
42
|
-
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm tracking-widest text-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
43
|
-
required autoComplete="one-time-code" autoFocus maxLength={6} />
|
|
44
|
-
</TibFormField>
|
|
45
|
-
<TibFormError error={error} />
|
|
46
|
-
<TibSubmitButton loading={loading} className="w-full">Verify email</TibSubmitButton>
|
|
47
|
-
</form>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default VerifyEmailPage;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export const PAGE_REGISTRY = [
|
|
2
|
-
{ id: 'login', category: 'auth', path: 'auth/login.tsx', name: 'LoginPage' },
|
|
3
|
-
{ id: 'signup', category: 'auth', path: 'auth/signup.tsx', name: 'SignupPage' },
|
|
4
|
-
{ id: 'verify-email', category: 'auth', path: 'auth/verify-email.tsx', name: 'VerifyEmailPage' },
|
|
5
|
-
{ id: 'forgot-password', category: 'auth', path: 'auth/forgot-password.tsx', name: 'ForgotPasswordPage' },
|
|
6
|
-
{ id: 'reset-password', category: 'auth', path: 'auth/reset-password.tsx', name: 'ResetPasswordPage' },
|
|
7
|
-
{ id: 'mfa-setup', category: 'auth', path: 'auth/mfa-setup.tsx', name: 'MfaSetupPage' },
|
|
8
|
-
{ id: 'mfa-verify', category: 'auth', path: 'auth/mfa-verify.tsx', name: 'MfaVerifyPage' },
|
|
9
|
-
{ id: 'accept-invitation', category: 'auth', path: 'auth/accept-invitation.tsx', name: 'AcceptInvitationPage' },
|
|
10
|
-
{ id: 'choose-org', category: 'auth', path: 'auth/choose-org.tsx', name: 'ChooseOrgPage' },
|
|
11
|
-
{ id: 'sign-out', category: 'auth', path: 'auth/sign-out.tsx', name: 'SignOutPage' },
|
|
12
|
-
{ id: 'dashboard-home', category: 'shell', path: 'shell/dashboard-home.tsx', name: 'DashboardHomePage' },
|
|
13
|
-
{ id: 'not-found', category: 'shell', path: 'shell/not-found.tsx', name: 'NotFoundPage' },
|
|
14
|
-
{ id: 'forbidden', category: 'shell', path: 'shell/forbidden.tsx', name: 'ForbiddenPage' },
|
|
15
|
-
{ id: 'server-error', category: 'shell', path: 'shell/server-error.tsx', name: 'ServerErrorPage' },
|
|
16
|
-
{ id: 'maintenance', category: 'shell', path: 'shell/maintenance.tsx', name: 'MaintenancePage' },
|
|
17
|
-
{ id: 'profile', category: 'account', path: 'account/profile.tsx', name: 'ProfilePage' },
|
|
18
|
-
{ id: 'workspace-settings', category: 'account', path: 'account/workspace-settings.tsx',name: 'WorkspaceSettingsPage' },
|
|
19
|
-
{ id: 'members', category: 'account', path: 'account/members.tsx', name: 'MembersPage' },
|
|
20
|
-
{ id: 'api-keys', category: 'account', path: 'account/api-keys.tsx', name: 'ApiKeysPage' },
|
|
21
|
-
{ id: 'audit-log', category: 'account', path: 'account/audit-log.tsx', name: 'AuditLogPage' },
|
|
22
|
-
] as const;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useApi } from '@mettlecast/sdk';
|
|
2
|
-
import { useProject } from '@mettlecast/dashboard-shell';
|
|
3
|
-
|
|
4
|
-
export function DashboardHomePage() {
|
|
5
|
-
const tib = useApi();
|
|
6
|
-
const { projectId, workspaceId } = useProject();
|
|
7
|
-
|
|
8
|
-
return (
|
|
9
|
-
<div className="p-6 space-y-6">
|
|
10
|
-
<div>
|
|
11
|
-
<h1 className="text-2xl font-semibold tracking-tight">Dashboard</h1>
|
|
12
|
-
<p className="text-sm text-muted-foreground mt-1">
|
|
13
|
-
Workspace <code className="text-xs font-mono bg-muted px-1 py-0.5 rounded">{workspaceId}</code>
|
|
14
|
-
{projectId && (
|
|
15
|
-
<> · Project <code className="text-xs font-mono bg-muted px-1 py-0.5 rounded">{projectId}</code></>
|
|
16
|
-
)}
|
|
17
|
-
</p>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
20
|
-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
21
|
-
<div className="rounded-lg border border-border bg-card p-6 space-y-2">
|
|
22
|
-
<h2 className="text-sm font-medium text-muted-foreground">Getting started</h2>
|
|
23
|
-
<p className="text-sm">Your domain modules will appear here once deployed.</p>
|
|
24
|
-
</div>
|
|
25
|
-
<div className="rounded-lg border border-border bg-card p-6 space-y-2">
|
|
26
|
-
<h2 className="text-sm font-medium text-muted-foreground">Activity</h2>
|
|
27
|
-
<p className="text-sm text-muted-foreground">No recent activity.</p>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="rounded-lg border border-border bg-card p-6 space-y-2">
|
|
30
|
-
<h2 className="text-sm font-medium text-muted-foreground">Status</h2>
|
|
31
|
-
<div className="flex items-center gap-2">
|
|
32
|
-
<div className="h-2 w-2 rounded-full bg-green-500" aria-hidden="true" />
|
|
33
|
-
<span className="text-sm">All systems operational</span>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default DashboardHomePage;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
2
|
-
|
|
3
|
-
type ForbiddenReason = 'TENANT_MISMATCH' | 'ROLE_DENIED' | 'GENERIC';
|
|
4
|
-
|
|
5
|
-
const MESSAGES: Record<ForbiddenReason, { title: string; description: string }> = {
|
|
6
|
-
TENANT_MISMATCH: {
|
|
7
|
-
title: 'Wrong workspace',
|
|
8
|
-
description: 'This resource belongs to a different workspace. Check the URL and try again.',
|
|
9
|
-
},
|
|
10
|
-
ROLE_DENIED: {
|
|
11
|
-
title: 'Insufficient permissions',
|
|
12
|
-
description: 'You don\'t have the required role to access this page. Contact your workspace admin.',
|
|
13
|
-
},
|
|
14
|
-
GENERIC: {
|
|
15
|
-
title: 'Access denied',
|
|
16
|
-
description: 'You don\'t have permission to view this page.',
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export function ForbiddenPage() {
|
|
21
|
-
const navigate = useNavigate();
|
|
22
|
-
const [params] = useSearchParams();
|
|
23
|
-
const reason = (params.get('reason') as ForbiddenReason | null) ?? 'GENERIC';
|
|
24
|
-
const { title, description } = MESSAGES[reason] ?? MESSAGES.GENERIC;
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
28
|
-
<div className="text-center space-y-4 max-w-md">
|
|
29
|
-
<p className="text-8xl font-bold text-muted-foreground/30 select-none" aria-hidden="true">403</p>
|
|
30
|
-
<h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
|
|
31
|
-
<p className="text-sm text-muted-foreground">{description}</p>
|
|
32
|
-
<div className="flex gap-3 justify-center pt-2">
|
|
33
|
-
<button
|
|
34
|
-
type="button"
|
|
35
|
-
onClick={() => navigate('/')}
|
|
36
|
-
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
37
|
-
>
|
|
38
|
-
Go to dashboard
|
|
39
|
-
</button>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default ForbiddenPage;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export function MaintenancePage() {
|
|
2
|
-
return (
|
|
3
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
4
|
-
<div className="text-center space-y-4 max-w-md">
|
|
5
|
-
<div className="text-5xl" role="img" aria-label="Under maintenance">🔧</div>
|
|
6
|
-
<h1 className="text-2xl font-semibold tracking-tight">Down for maintenance</h1>
|
|
7
|
-
<p className="text-sm text-muted-foreground">
|
|
8
|
-
We're making improvements. Please check back shortly.
|
|
9
|
-
</p>
|
|
10
|
-
<p className="text-xs text-muted-foreground">
|
|
11
|
-
Follow{' '}
|
|
12
|
-
<a
|
|
13
|
-
href="https://status.mettlecast.com"
|
|
14
|
-
target="_blank"
|
|
15
|
-
rel="noopener noreferrer"
|
|
16
|
-
className="underline underline-offset-4 hover:text-foreground"
|
|
17
|
-
>
|
|
18
|
-
status.mettlecast.com
|
|
19
|
-
</a>
|
|
20
|
-
{' '}for updates.
|
|
21
|
-
</p>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default MaintenancePage;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { useNavigate } from 'react-router-dom';
|
|
2
|
-
|
|
3
|
-
export function NotFoundPage() {
|
|
4
|
-
const navigate = useNavigate();
|
|
5
|
-
return (
|
|
6
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
7
|
-
<div className="text-center space-y-4 max-w-md">
|
|
8
|
-
<p className="text-8xl font-bold text-muted-foreground/30 select-none" aria-hidden="true">404</p>
|
|
9
|
-
<h1 className="text-2xl font-semibold tracking-tight">Page not found</h1>
|
|
10
|
-
<p className="text-sm text-muted-foreground">
|
|
11
|
-
The page you're looking for doesn't exist or has been moved.
|
|
12
|
-
</p>
|
|
13
|
-
<div className="flex gap-3 justify-center pt-2">
|
|
14
|
-
<button
|
|
15
|
-
type="button"
|
|
16
|
-
onClick={() => navigate(-1)}
|
|
17
|
-
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4"
|
|
18
|
-
>
|
|
19
|
-
Go back
|
|
20
|
-
</button>
|
|
21
|
-
<button
|
|
22
|
-
type="button"
|
|
23
|
-
onClick={() => navigate('/')}
|
|
24
|
-
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
25
|
-
>
|
|
26
|
-
Go to dashboard
|
|
27
|
-
</button>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export default NotFoundPage;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useNavigate } from 'react-router-dom';
|
|
2
|
-
|
|
3
|
-
interface ServerErrorPageProps {
|
|
4
|
-
traceId?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function ServerErrorPage({ traceId }: ServerErrorPageProps = {}) {
|
|
8
|
-
const navigate = useNavigate();
|
|
9
|
-
|
|
10
|
-
return (
|
|
11
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
|
12
|
-
<div className="text-center space-y-4 max-w-md">
|
|
13
|
-
<p className="text-8xl font-bold text-muted-foreground/30 select-none" aria-hidden="true">500</p>
|
|
14
|
-
<h1 className="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
15
|
-
<p className="text-sm text-muted-foreground">
|
|
16
|
-
An unexpected error occurred. Our team has been notified.
|
|
17
|
-
</p>
|
|
18
|
-
{traceId && (
|
|
19
|
-
<p className="text-xs text-muted-foreground font-mono">
|
|
20
|
-
Trace ID: {traceId}
|
|
21
|
-
</p>
|
|
22
|
-
)}
|
|
23
|
-
<div className="flex gap-3 justify-center pt-2">
|
|
24
|
-
<button
|
|
25
|
-
type="button"
|
|
26
|
-
onClick={() => window.location.reload()}
|
|
27
|
-
className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-4"
|
|
28
|
-
>
|
|
29
|
-
Retry
|
|
30
|
-
</button>
|
|
31
|
-
<button
|
|
32
|
-
type="button"
|
|
33
|
-
onClick={() => navigate('/')}
|
|
34
|
-
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
35
|
-
>
|
|
36
|
-
Go to dashboard
|
|
37
|
-
</button>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default ServerErrorPage;
|