@mettlecast/domain-cli 0.2.1 → 0.2.3

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.
Files changed (48) hide show
  1. package/dist/builder/load-module.js +0 -10
  2. package/dist/cli.js +9 -1
  3. package/dist/commands/add-page.js +39 -126
  4. package/dist/commands/build-ui/component-scanner.d.ts +12 -0
  5. package/dist/commands/build-ui/component-scanner.js +50 -0
  6. package/dist/commands/build-ui/page-indexer.d.ts +13 -0
  7. package/dist/commands/build-ui/page-indexer.js +107 -0
  8. package/dist/commands/build-ui.d.ts +3 -0
  9. package/dist/commands/build-ui.js +23 -0
  10. package/dist/templates/api-skeleton.js +1 -1
  11. package/dist/templates/events-skeleton.js +1 -1
  12. package/dist/templates/subscriber-skeleton.js +1 -1
  13. package/package.json +1 -1
  14. package/src/builder/load-module.ts +0 -12
  15. package/src/cli.ts +10 -1
  16. package/src/commands/add-page.ts +40 -156
  17. package/src/commands/build-ui/component-scanner.ts +70 -0
  18. package/src/commands/build-ui/page-indexer.ts +134 -0
  19. package/src/commands/build-ui.ts +29 -0
  20. package/src/templates/api-skeleton.ts +1 -1
  21. package/src/templates/events-skeleton.ts +1 -1
  22. package/src/templates/subscriber-skeleton.ts +1 -1
  23. package/dist/templates/dashboard-pages/index.d.ts +0 -101
  24. package/dist/templates/dashboard-pages/index.js +0 -22
  25. package/src/templates/dashboard-pages/account/api-keys.tsx +0 -107
  26. package/src/templates/dashboard-pages/account/audit-log.tsx +0 -60
  27. package/src/templates/dashboard-pages/account/index.ts +0 -5
  28. package/src/templates/dashboard-pages/account/members.tsx +0 -107
  29. package/src/templates/dashboard-pages/account/profile.tsx +0 -53
  30. package/src/templates/dashboard-pages/account/workspace-settings.tsx +0 -64
  31. package/src/templates/dashboard-pages/auth/accept-invitation.tsx +0 -62
  32. package/src/templates/dashboard-pages/auth/choose-org.tsx +0 -66
  33. package/src/templates/dashboard-pages/auth/forgot-password.tsx +0 -69
  34. package/src/templates/dashboard-pages/auth/index.ts +0 -10
  35. package/src/templates/dashboard-pages/auth/login.tsx +0 -75
  36. package/src/templates/dashboard-pages/auth/mfa-setup.tsx +0 -56
  37. package/src/templates/dashboard-pages/auth/mfa-verify.tsx +0 -49
  38. package/src/templates/dashboard-pages/auth/reset-password.tsx +0 -64
  39. package/src/templates/dashboard-pages/auth/sign-out.tsx +0 -20
  40. package/src/templates/dashboard-pages/auth/signup.tsx +0 -71
  41. package/src/templates/dashboard-pages/auth/verify-email.tsx +0 -53
  42. package/src/templates/dashboard-pages/index.ts +0 -22
  43. package/src/templates/dashboard-pages/shell/dashboard-home.tsx +0 -41
  44. package/src/templates/dashboard-pages/shell/forbidden.tsx +0 -46
  45. package/src/templates/dashboard-pages/shell/index.ts +0 -5
  46. package/src/templates/dashboard-pages/shell/maintenance.tsx +0 -27
  47. package/src/templates/dashboard-pages/shell/not-found.tsx +0 -34
  48. package/src/templates/dashboard-pages/shell/server-error.tsx +0 -44
@@ -1,107 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { useApi } from '@mettlecast/sdk';
3
- import { useProject } from '@mettlecast/dashboard-shell';
4
- import { TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
5
-
6
- interface ApiKey {
7
- keyId: string;
8
- label: string;
9
- prefix: string;
10
- createdAt: string;
11
- }
12
-
13
- export function ApiKeysPage() {
14
- const tib = useApi();
15
- const { workspaceId } = useProject();
16
- const [keys, setKeys] = useState<ApiKey[]>([]);
17
- const [label, setLabel] = useState('');
18
- const [newKey, setNewKey] = useState<string | null>(null);
19
- const [error, setError] = useState<string | null>(null);
20
- const [loading, setLoading] = useState(true);
21
- const [creating, setCreating] = useState(false);
22
-
23
- useEffect(() => {
24
- if (!workspaceId) return;
25
- tib?.listApiKeys(workspaceId)
26
- .then(res => setKeys((res as any)?.items ?? []))
27
- .catch(err => setError(err instanceof Error ? err.message : 'Failed to load API keys'))
28
- .finally(() => setLoading(false));
29
- }, [tib, workspaceId]);
30
-
31
- async function handleCreate(e: React.FormEvent) {
32
- e.preventDefault();
33
- if (!workspaceId) return;
34
- setCreating(true);
35
- setError(null);
36
- setNewKey(null);
37
- try {
38
- const res = await tib?.createApiKey(workspaceId, { label }) as any;
39
- setNewKey(res?.key ?? null);
40
- setLabel('');
41
- const refreshed = await tib?.listApiKeys(workspaceId) as any;
42
- setKeys(refreshed?.items ?? []);
43
- } catch (err) {
44
- setError(err instanceof Error ? err.message : 'Create failed');
45
- } finally {
46
- setCreating(false);
47
- }
48
- }
49
-
50
- return (
51
- <div className="p-6 max-w-2xl space-y-8">
52
- <div>
53
- <h1 className="text-2xl font-semibold tracking-tight">API keys</h1>
54
- <p className="text-sm text-muted-foreground mt-1">Manage programmatic access credentials</p>
55
- </div>
56
-
57
- {newKey && (
58
- <div className="rounded-md bg-green-50 dark:bg-green-950 border border-green-200 dark:border-green-800 p-4 space-y-1" role="alert">
59
- <p className="text-sm font-medium text-green-800 dark:text-green-200">API key created — copy it now, it won't be shown again</p>
60
- <code className="block text-xs font-mono bg-white dark:bg-black rounded px-2 py-1 break-all">{newKey}</code>
61
- </div>
62
- )}
63
-
64
- <form onSubmit={handleCreate} className="flex gap-2 items-end">
65
- <div className="flex-1">
66
- <label htmlFor="label" className="text-sm font-medium">Label</label>
67
- <input id="label" type="text" value={label} onChange={e => setLabel(e.target.value)}
68
- className="mt-1 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"
69
- placeholder="My integration" required />
70
- </div>
71
- <TibSubmitButton loading={creating}>Create key</TibSubmitButton>
72
- </form>
73
- <TibFormError error={error} />
74
-
75
- {loading ? <p className="text-sm text-muted-foreground">Loading…</p> : (
76
- <table className="w-full text-sm">
77
- <thead>
78
- <tr className="border-b border-border">
79
- <th className="text-left py-2 font-medium text-muted-foreground">Label</th>
80
- <th className="text-left py-2 font-medium text-muted-foreground">Key prefix</th>
81
- <th className="text-left py-2 font-medium text-muted-foreground">Created</th>
82
- <th className="py-2" />
83
- </tr>
84
- </thead>
85
- <tbody>
86
- {keys.map(k => (
87
- <tr key={k.keyId} className="border-b border-border/50">
88
- <td className="py-3 font-medium">{k.label}</td>
89
- <td className="py-3 font-mono text-xs">{k.prefix}…</td>
90
- <td className="py-3 text-muted-foreground">{new Date(k.createdAt).toLocaleDateString()}</td>
91
- <td className="py-3 text-right">
92
- <button type="button"
93
- onClick={() => window.confirm('Revoke this key?') && workspaceId && tib?.revokeApiKey(workspaceId, k.keyId).then(() => setKeys(keys.filter(x => x.keyId !== k.keyId)))}
94
- className="text-xs text-destructive hover:underline underline-offset-4">
95
- Revoke
96
- </button>
97
- </td>
98
- </tr>
99
- ))}
100
- </tbody>
101
- </table>
102
- )}
103
- </div>
104
- );
105
- }
106
-
107
- export default ApiKeysPage;
@@ -1,60 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { useApi } from '@mettlecast/sdk';
3
- import { useProject } from '@mettlecast/dashboard-shell';
4
- import { TibFormError } from '../../components/ui/mc-form';
5
-
6
- interface ActivityEvent {
7
- id: string;
8
- type: string;
9
- actor: string;
10
- summary: string;
11
- createdAt: string;
12
- }
13
-
14
- export function AuditLogPage() {
15
- const tib = useApi();
16
- const { workspaceId } = useProject();
17
- const [events, setEvents] = useState<ActivityEvent[]>([]);
18
- const [error, setError] = useState<string | null>(null);
19
- const [loading, setLoading] = useState(true);
20
-
21
- useEffect(() => {
22
- if (!workspaceId) return;
23
- tib?.listActivity(workspaceId, { limit: 50 })
24
- .then(res => setEvents((res as any)?.items ?? []))
25
- .catch(err => setError(err instanceof Error ? err.message : 'Failed to load audit log'))
26
- .finally(() => setLoading(false));
27
- }, [tib, workspaceId]);
28
-
29
- return (
30
- <div className="p-6 max-w-3xl space-y-6">
31
- <div>
32
- <h1 className="text-2xl font-semibold tracking-tight">Audit log</h1>
33
- <p className="text-sm text-muted-foreground mt-1">Recent activity in this workspace</p>
34
- </div>
35
- <TibFormError error={error} />
36
- {loading ? (
37
- <p className="text-sm text-muted-foreground">Loading…</p>
38
- ) : events.length === 0 ? (
39
- <p className="text-sm text-muted-foreground py-8 text-center">No activity yet</p>
40
- ) : (
41
- <ol className="space-y-3" aria-label="Audit log">
42
- {events.map(ev => (
43
- <li key={ev.id} className="flex gap-4 text-sm">
44
- <time dateTime={ev.createdAt} className="w-36 shrink-0 text-xs text-muted-foreground pt-0.5">
45
- {new Date(ev.createdAt).toLocaleString()}
46
- </time>
47
- <div>
48
- <span className="font-medium">{ev.actor}</span>
49
- {' '}
50
- <span className="text-muted-foreground">{ev.summary}</span>
51
- </div>
52
- </li>
53
- ))}
54
- </ol>
55
- )}
56
- </div>
57
- );
58
- }
59
-
60
- export default AuditLogPage;
@@ -1,5 +0,0 @@
1
- export { ProfilePage } from './profile';
2
- export { WorkspaceSettingsPage } from './workspace-settings';
3
- export { MembersPage } from './members';
4
- export { ApiKeysPage } from './api-keys';
5
- export { AuditLogPage } from './audit-log';
@@ -1,107 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { useApi } from '@mettlecast/sdk';
3
- import { useProject } from '@mettlecast/dashboard-shell';
4
- import { TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
5
-
6
- interface Member {
7
- userId: string;
8
- email: string;
9
- name?: string;
10
- role: string;
11
- }
12
-
13
- export function MembersPage() {
14
- const tib = useApi();
15
- const { workspaceId } = useProject();
16
- const [members, setMembers] = useState<Member[]>([]);
17
- const [inviteEmail, setInviteEmail] = useState('');
18
- const [inviteRole, setInviteRole] = useState('member');
19
- const [error, setError] = useState<string | null>(null);
20
- const [loading, setLoading] = useState(true);
21
- const [inviting, setInviting] = useState(false);
22
-
23
- useEffect(() => {
24
- if (!workspaceId) return;
25
- tib?.listMembers(workspaceId)
26
- .then(res => setMembers((res as any)?.items ?? []))
27
- .catch(err => setError(err instanceof Error ? err.message : 'Failed to load members'))
28
- .finally(() => setLoading(false));
29
- }, [tib, workspaceId]);
30
-
31
- async function handleInvite(e: React.FormEvent) {
32
- e.preventDefault();
33
- if (!workspaceId) return;
34
- setInviting(true);
35
- setError(null);
36
- try {
37
- await tib?.inviteMember(workspaceId, { email: inviteEmail, role: inviteRole });
38
- setInviteEmail('');
39
- } catch (err) {
40
- setError(err instanceof Error ? err.message : 'Invite failed');
41
- } finally {
42
- setInviting(false);
43
- }
44
- }
45
-
46
- return (
47
- <div className="p-6 max-w-3xl space-y-8">
48
- <div>
49
- <h1 className="text-2xl font-semibold tracking-tight">Members</h1>
50
- <p className="text-sm text-muted-foreground mt-1">Manage workspace members and roles</p>
51
- </div>
52
-
53
- <form onSubmit={handleInvite} className="flex gap-2 items-end">
54
- <div className="flex-1">
55
- <label htmlFor="inviteEmail" className="text-sm font-medium">Email</label>
56
- <input id="inviteEmail" type="email" value={inviteEmail} onChange={e => setInviteEmail(e.target.value)}
57
- className="mt-1 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"
58
- placeholder="colleague@company.com" required />
59
- </div>
60
- <div>
61
- <label htmlFor="inviteRole" className="text-sm font-medium">Role</label>
62
- <select id="inviteRole" value={inviteRole} onChange={e => setInviteRole(e.target.value)}
63
- className="mt-1 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">
64
- <option value="member">Member</option>
65
- <option value="admin">Admin</option>
66
- <option value="viewer">Viewer</option>
67
- </select>
68
- </div>
69
- <TibSubmitButton loading={inviting}>Invite</TibSubmitButton>
70
- </form>
71
- <TibFormError error={error} />
72
-
73
- {loading ? (
74
- <p className="text-sm text-muted-foreground">Loading…</p>
75
- ) : (
76
- <table className="w-full text-sm">
77
- <thead>
78
- <tr className="border-b border-border">
79
- <th className="text-left py-2 font-medium text-muted-foreground">Member</th>
80
- <th className="text-left py-2 font-medium text-muted-foreground">Role</th>
81
- <th className="py-2" />
82
- </tr>
83
- </thead>
84
- <tbody>
85
- {members.map(m => (
86
- <tr key={m.userId} className="border-b border-border/50">
87
- <td className="py-3">
88
- <div className="font-medium">{m.name ?? m.email}</div>
89
- {m.name && <div className="text-xs text-muted-foreground">{m.email}</div>}
90
- </td>
91
- <td className="py-3 capitalize">{m.role}</td>
92
- <td className="py-3 text-right">
93
- <button type="button" onClick={() => workspaceId && tib?.removeMember(workspaceId, m.userId)}
94
- className="text-xs text-destructive hover:underline underline-offset-4">
95
- Remove
96
- </button>
97
- </td>
98
- </tr>
99
- ))}
100
- </tbody>
101
- </table>
102
- )}
103
- </div>
104
- );
105
- }
106
-
107
- export default MembersPage;
@@ -1,53 +0,0 @@
1
- import { useState } from 'react';
2
- import { useApi } from '@mettlecast/sdk';
3
- import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
4
-
5
- export function ProfilePage() {
6
- const tib = useApi();
7
- const [name, setName] = useState('');
8
- const [email] = useState('');
9
- const [error, setError] = useState<string | null>(null);
10
- const [loading, setLoading] = useState(false);
11
- const [saved, setSaved] = useState(false);
12
-
13
- async function handleSubmit(e: React.FormEvent) {
14
- e.preventDefault();
15
- setLoading(true);
16
- setError(null);
17
- setSaved(false);
18
- try {
19
- await (tib as any)?.workspace?.update({ name });
20
- setSaved(true);
21
- } catch (err) {
22
- setError(err instanceof Error ? err.message : 'Save failed');
23
- } finally {
24
- setLoading(false);
25
- }
26
- }
27
-
28
- return (
29
- <div className="p-6 max-w-2xl space-y-8">
30
- <div>
31
- <h1 className="text-2xl font-semibold tracking-tight">Profile</h1>
32
- <p className="text-sm text-muted-foreground mt-1">Manage your personal information</p>
33
- </div>
34
- <form onSubmit={handleSubmit} className="space-y-4">
35
- <h2 className="text-lg font-semibold">Personal details</h2>
36
- <TibFormField name="name" label="Full name">
37
- <input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
38
- 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"
39
- autoComplete="name" />
40
- </TibFormField>
41
- <TibFormField name="email" label="Email">
42
- <input id="email" type="email" value={email} readOnly
43
- className="w-full rounded-md border border-input bg-muted px-3 py-2 text-sm text-muted-foreground cursor-not-allowed" />
44
- </TibFormField>
45
- {saved && <p className="text-sm text-green-600" role="status">Changes saved</p>}
46
- <TibFormError error={error} />
47
- <TibSubmitButton loading={loading}>Save changes</TibSubmitButton>
48
- </form>
49
- </div>
50
- );
51
- }
52
-
53
- export default ProfilePage;
@@ -1,64 +0,0 @@
1
- import { useState } from 'react';
2
- import { useApi } from '@mettlecast/sdk';
3
- import { useProject } from '@mettlecast/dashboard-shell';
4
- import { TibFormField, TibFormError, TibSubmitButton } from '../../components/ui/mc-form';
5
-
6
- export function WorkspaceSettingsPage() {
7
- const tib = useApi();
8
- const { workspaceId } = useProject();
9
- const [name, setName] = useState('');
10
- const [error, setError] = useState<string | null>(null);
11
- const [loading, setLoading] = useState(false);
12
- const [saved, setSaved] = useState(false);
13
-
14
- async function handleSubmit(e: React.FormEvent) {
15
- e.preventDefault();
16
- setLoading(true);
17
- setError(null);
18
- setSaved(false);
19
- try {
20
- await (tib as any)?.workspace?.update({ workspaceId, name });
21
- setSaved(true);
22
- } catch (err) {
23
- setError(err instanceof Error ? err.message : 'Save failed');
24
- } finally {
25
- setLoading(false);
26
- }
27
- }
28
-
29
- return (
30
- <div className="p-6 max-w-2xl space-y-8">
31
- <div>
32
- <h1 className="text-2xl font-semibold tracking-tight">Workspace settings</h1>
33
- <p className="text-sm text-muted-foreground mt-1">Manage workspace configuration</p>
34
- </div>
35
- <form onSubmit={handleSubmit} className="space-y-4">
36
- <h2 className="text-lg font-semibold">General</h2>
37
- <TibFormField name="name" label="Workspace name">
38
- <input id="name" type="text" value={name} onChange={e => setName(e.target.value)}
39
- 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"
40
- autoComplete="organization" />
41
- </TibFormField>
42
- <TibFormField name="workspaceId" label="Workspace ID">
43
- <input id="workspaceId" type="text" value={workspaceId ?? ''} readOnly
44
- className="w-full rounded-md border border-input bg-muted px-3 py-2 text-sm text-muted-foreground font-mono cursor-not-allowed" />
45
- </TibFormField>
46
- {saved && <p className="text-sm text-green-600" role="status">Changes saved</p>}
47
- <TibFormError error={error} />
48
- <TibSubmitButton loading={loading}>Save changes</TibSubmitButton>
49
- </form>
50
-
51
- <div className="space-y-4 border-t border-border pt-8">
52
- <h2 className="text-lg font-semibold text-destructive">Danger zone</h2>
53
- <p className="text-sm text-muted-foreground">These actions are irreversible.</p>
54
- <button type="button"
55
- className="rounded-md border border-destructive px-4 py-2 text-sm font-medium text-destructive hover:bg-destructive hover:text-destructive-foreground transition-colors"
56
- onClick={() => window.confirm('Are you sure? This cannot be undone.') && (tib as any)?.workspace?.delete({ workspaceId })}>
57
- Delete workspace
58
- </button>
59
- </div>
60
- </div>
61
- );
62
- }
63
-
64
- export default WorkspaceSettingsPage;
@@ -1,62 +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 AcceptInvitationPage() {
7
- const tib = useApi();
8
- const navigate = useNavigate();
9
- const [searchParams] = useSearchParams();
10
- const token = searchParams.get('token') ?? '';
11
- const email = searchParams.get('email') ?? '';
12
- const [name, setName] = useState('');
13
- const [password, setPassword] = 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
- setLoading(true);
20
- setError(null);
21
- try {
22
- await (tib as any)?.auth?.acceptInvite({ token, name, password });
23
- navigate('/login');
24
- } catch (err) {
25
- setError(err instanceof Error ? err.message : 'Failed to accept invitation');
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">Accept invitation</h1>
36
- <p className="text-sm text-muted-foreground mt-1">You've been invited to join the workspace</p>
37
- </div>
38
- <form onSubmit={handleSubmit} className="space-y-4" noValidate>
39
- {email && (
40
- <div className="rounded-md bg-muted px-3 py-2 text-sm">
41
- Signing up as <strong>{email}</strong>
42
- </div>
43
- )}
44
- <TibFormField name="name" label="Full name">
45
- <input id="name" type="text" value={name} onChange={e => setName(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="name" autoFocus />
48
- </TibFormField>
49
- <TibFormField name="password" label="Create password">
50
- <input id="password" type="password" value={password} onChange={e => setPassword(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="new-password" minLength={8} />
53
- </TibFormField>
54
- <TibFormError error={error} />
55
- <TibSubmitButton loading={loading} className="w-full">Join workspace</TibSubmitButton>
56
- </form>
57
- </div>
58
- </div>
59
- );
60
- }
61
-
62
- export default AcceptInvitationPage;
@@ -1,66 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { useNavigate } from 'react-router-dom';
3
- import { useApi } from '@mettlecast/sdk';
4
- import { TibFormError } from '../../components/ui/mc-form';
5
-
6
- interface OrgItem {
7
- orgId: string;
8
- name: string;
9
- role: string;
10
- }
11
-
12
- export function ChooseOrgPage() {
13
- const tib = useApi();
14
- const navigate = useNavigate();
15
- const [orgs, setOrgs] = useState<OrgItem[]>([]);
16
- const [error, setError] = useState<string | null>(null);
17
- const [loading, setLoading] = useState(true);
18
-
19
- useEffect(() => {
20
- (async () => {
21
- try {
22
- const res = await (tib as any)?.org?.listWorkspaces({});
23
- setOrgs((res?.items ?? []) as OrgItem[]);
24
- } catch (err) {
25
- setError(err instanceof Error ? err.message : 'Failed to load organisations');
26
- } finally {
27
- setLoading(false);
28
- }
29
- })();
30
- }, [tib]);
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">Choose organisation</h1>
37
- <p className="text-sm text-muted-foreground mt-1">Select the workspace to continue</p>
38
- </div>
39
- <TibFormError error={error} />
40
- {loading ? (
41
- <p className="text-sm text-muted-foreground">Loading…</p>
42
- ) : (
43
- <ul className="space-y-2" role="list">
44
- {orgs.map(org => (
45
- <li key={org.orgId}>
46
- <button
47
- type="button"
48
- onClick={() => navigate(`/t/${org.orgId}`)}
49
- className="w-full text-left rounded-md border border-border px-4 py-3 text-sm hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
50
- >
51
- <div className="font-medium">{org.name}</div>
52
- <div className="text-xs text-muted-foreground capitalize">{org.role}</div>
53
- </button>
54
- </li>
55
- ))}
56
- {orgs.length === 0 && (
57
- <li className="text-sm text-muted-foreground text-center py-4">No organisations found</li>
58
- )}
59
- </ul>
60
- )}
61
- </div>
62
- </div>
63
- );
64
- }
65
-
66
- export default ChooseOrgPage;
@@ -1,69 +0,0 @@
1
- import { useState } from 'react';
2
- import { 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 ForgotPasswordPage() {
7
- const tib = useApi();
8
- const [email, setEmail] = useState('');
9
- const [sent, setSent] = useState(false);
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?.forgotPassword({ email });
19
- setSent(true);
20
- } catch (err) {
21
- setError(err instanceof Error ? err.message : 'Failed to send reset email');
22
- } finally {
23
- setLoading(false);
24
- }
25
- }
26
-
27
- if (sent) {
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-4 text-center">
31
- <h1 className="text-2xl font-semibold tracking-tight">Check your email</h1>
32
- <p className="text-sm text-muted-foreground">
33
- We sent a password reset link to <strong>{email}</strong>
34
- </p>
35
- <Link to="/login" className="text-sm text-primary hover:underline underline-offset-4">
36
- Back to sign in
37
- </Link>
38
- </div>
39
- </div>
40
- );
41
- }
42
-
43
- return (
44
- <div className="min-h-screen flex items-center justify-center bg-background p-4">
45
- <div className="w-full max-w-md space-y-6">
46
- <div>
47
- <h1 className="text-2xl font-semibold tracking-tight">Reset password</h1>
48
- <p className="text-sm text-muted-foreground mt-1">Enter your email to receive a reset link</p>
49
- </div>
50
- <form onSubmit={handleSubmit} className="space-y-4" noValidate>
51
- <TibFormField name="email" label="Email">
52
- <input id="email" type="email" value={email} onChange={e => setEmail(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="email" autoFocus />
55
- </TibFormField>
56
- <TibFormError error={error} />
57
- <TibSubmitButton loading={loading} className="w-full">Send reset link</TibSubmitButton>
58
- </form>
59
- <p className="text-center">
60
- <Link to="/login" className="text-sm text-muted-foreground hover:text-foreground">
61
- ← Back to sign in
62
- </Link>
63
- </p>
64
- </div>
65
- </div>
66
- );
67
- }
68
-
69
- export default ForgotPasswordPage;
@@ -1,10 +0,0 @@
1
- export { LoginPage } from './login';
2
- export { SignupPage } from './signup';
3
- export { VerifyEmailPage } from './verify-email';
4
- export { ForgotPasswordPage } from './forgot-password';
5
- export { ResetPasswordPage } from './reset-password';
6
- export { MfaSetupPage } from './mfa-setup';
7
- export { MfaVerifyPage } from './mfa-verify';
8
- export { AcceptInvitationPage } from './accept-invitation';
9
- export { ChooseOrgPage } from './choose-org';
10
- export { SignOutPage } from './sign-out';