@assistkick/create 1.0.0

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 (178) hide show
  1. package/dist/bin/create.d.ts +2 -0
  2. package/dist/bin/create.js +25 -0
  3. package/dist/bin/create.js.map +1 -0
  4. package/dist/src/scaffolder.d.ts +22 -0
  5. package/dist/src/scaffolder.js +120 -0
  6. package/dist/src/scaffolder.js.map +1 -0
  7. package/package.json +24 -0
  8. package/templates/product-system/.env.example +8 -0
  9. package/templates/product-system/CLAUDE.md +45 -0
  10. package/templates/product-system/package.json +32 -0
  11. package/templates/product-system/packages/backend/package.json +37 -0
  12. package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
  13. package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
  14. package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
  15. package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
  16. package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
  17. package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
  18. package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
  19. package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
  20. package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
  21. package/templates/product-system/packages/backend/src/server.ts +159 -0
  22. package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
  23. package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
  24. package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
  25. package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
  26. package/templates/product-system/packages/backend/src/services/init.ts +80 -0
  27. package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
  28. package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
  29. package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
  30. package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
  31. package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
  32. package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
  33. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
  34. package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
  35. package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
  36. package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
  37. package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
  38. package/templates/product-system/packages/backend/tsconfig.json +22 -0
  39. package/templates/product-system/packages/frontend/index.html +13 -0
  40. package/templates/product-system/packages/frontend/package-lock.json +2666 -0
  41. package/templates/product-system/packages/frontend/package.json +30 -0
  42. package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
  43. package/templates/product-system/packages/frontend/src/App.tsx +29 -0
  44. package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
  45. package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
  46. package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
  47. package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
  48. package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
  49. package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
  50. package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
  51. package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
  52. package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
  53. package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
  54. package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
  55. package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
  56. package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
  57. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
  58. package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
  59. package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
  60. package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
  61. package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
  62. package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
  63. package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
  64. package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
  65. package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
  66. package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
  67. package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
  68. package/templates/product-system/packages/frontend/src/main.tsx +12 -0
  69. package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
  70. package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
  71. package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
  72. package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
  73. package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
  74. package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
  75. package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
  76. package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
  77. package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
  78. package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
  79. package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
  80. package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
  81. package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
  82. package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
  83. package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
  84. package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
  85. package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
  86. package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
  87. package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
  88. package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
  89. package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
  90. package/templates/product-system/packages/frontend/tsconfig.json +21 -0
  91. package/templates/product-system/packages/frontend/vite.config.ts +20 -0
  92. package/templates/product-system/packages/shared/.env.example +3 -0
  93. package/templates/product-system/packages/shared/README.md +1 -0
  94. package/templates/product-system/packages/shared/db/migrate.ts +32 -0
  95. package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
  96. package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
  97. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
  98. package/templates/product-system/packages/shared/db/schema.ts +137 -0
  99. package/templates/product-system/packages/shared/drizzle.config.js +14 -0
  100. package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
  101. package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
  102. package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
  103. package/templates/product-system/packages/shared/lib/constants.ts +327 -0
  104. package/templates/product-system/packages/shared/lib/db.ts +81 -0
  105. package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
  106. package/templates/product-system/packages/shared/lib/graph.ts +186 -0
  107. package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
  108. package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
  109. package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
  110. package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
  111. package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
  112. package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
  113. package/templates/product-system/packages/shared/lib/session.ts +152 -0
  114. package/templates/product-system/packages/shared/lib/validator.ts +117 -0
  115. package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
  116. package/templates/product-system/packages/shared/package.json +30 -0
  117. package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
  118. package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
  119. package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
  120. package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
  121. package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
  122. package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
  123. package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
  124. package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
  125. package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
  126. package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
  127. package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
  128. package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
  129. package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
  130. package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
  131. package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
  132. package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
  133. package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
  134. package/templates/product-system/packages/shared/tsconfig.json +24 -0
  135. package/templates/product-system/pnpm-workspace.yaml +2 -0
  136. package/templates/product-system/smoke_test.ts +219 -0
  137. package/templates/product-system/tests/coherence_review.test.ts +562 -0
  138. package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
  139. package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
  140. package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
  141. package/templates/product-system/tests/feature_kind.test.ts +139 -0
  142. package/templates/product-system/tests/gap_indicators.test.ts +199 -0
  143. package/templates/product-system/tests/graceful_init.test.ts +142 -0
  144. package/templates/product-system/tests/graph_legend.test.ts +314 -0
  145. package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
  146. package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
  147. package/templates/product-system/tests/kanban.test.ts +529 -0
  148. package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
  149. package/templates/product-system/tests/node_search.test.ts +340 -0
  150. package/templates/product-system/tests/node_sizing.test.ts +170 -0
  151. package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
  152. package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
  153. package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
  154. package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
  155. package/templates/product-system/tests/pipeline.test.ts +195 -0
  156. package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
  157. package/templates/product-system/tests/play_all.test.ts +296 -0
  158. package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
  159. package/templates/product-system/tests/relevance_search.test.ts +186 -0
  160. package/templates/product-system/tests/search_reorder.test.ts +88 -0
  161. package/templates/product-system/tests/serve_ui.test.ts +281 -0
  162. package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
  163. package/templates/product-system/tests/session_context_recall.test.ts +135 -0
  164. package/templates/product-system/tests/side_panel.test.ts +345 -0
  165. package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
  166. package/templates/product-system/tests/url_routing_test.ts +122 -0
  167. package/templates/product-system/tests/user_login.test.ts +150 -0
  168. package/templates/product-system/tests/user_registration.test.ts +205 -0
  169. package/templates/product-system/tests/web_terminal.test.ts +572 -0
  170. package/templates/product-system/tests/work_summary.test.ts +211 -0
  171. package/templates/product-system/tests/zoom_pan.test.ts +43 -0
  172. package/templates/product-system/tsconfig.json +24 -0
  173. package/templates/skills/product-bootstrap/SKILL.md +312 -0
  174. package/templates/skills/product-code-reviewer/SKILL.md +147 -0
  175. package/templates/skills/product-debugger/SKILL.md +206 -0
  176. package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
  177. package/templates/skills/product-developer/SKILL.md +182 -0
  178. package/templates/skills/product-interview/SKILL.md +220 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Accept Invitation Page — invited user sets password to complete registration.
3
+ * Token and email are extracted from URL query parameters.
4
+ */
5
+
6
+ import React, { useState, useCallback, useEffect } from 'react';
7
+ import { Link, useSearchParams } from 'react-router-dom';
8
+ import { useTheme } from '../hooks/useTheme';
9
+ import { apiClient } from '../api/client';
10
+ import { validatePassword } from '../utils/auth_validation';
11
+
12
+ interface AcceptInvitationPageProps {
13
+ onSuccess: () => void;
14
+ }
15
+
16
+ export function AcceptInvitationPage({ onSuccess }: AcceptInvitationPageProps) {
17
+ const { theme, toggleTheme } = useTheme();
18
+ const [searchParams] = useSearchParams();
19
+ const token = searchParams.get('token') || '';
20
+ const email = searchParams.get('email') || '';
21
+
22
+ const [password, setPassword] = useState('');
23
+ const [confirmPassword, setConfirmPassword] = useState('');
24
+ const [error, setError] = useState('');
25
+ const [fieldError, setFieldError] = useState('');
26
+ const [submitting, setSubmitting] = useState(false);
27
+ const [validating, setValidating] = useState(true);
28
+ const [valid, setValid] = useState(false);
29
+
30
+ useEffect(() => {
31
+ if (!token || !email) {
32
+ setValidating(false);
33
+ return;
34
+ }
35
+
36
+ apiClient.validateInvitation(token, email)
37
+ .then(data => {
38
+ setValid(data.valid);
39
+ setValidating(false);
40
+ })
41
+ .catch(() => {
42
+ setValid(false);
43
+ setValidating(false);
44
+ });
45
+ }, [token, email]);
46
+
47
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
48
+ e.preventDefault();
49
+ setError('');
50
+ setFieldError('');
51
+
52
+ const passErr = validatePassword(password);
53
+ if (passErr) {
54
+ setFieldError(passErr);
55
+ return;
56
+ }
57
+
58
+ if (password !== confirmPassword) {
59
+ setFieldError('Passwords do not match');
60
+ return;
61
+ }
62
+
63
+ setSubmitting(true);
64
+ try {
65
+ await apiClient.acceptInvitation(token, email, password);
66
+ onSuccess();
67
+ } catch (err: any) {
68
+ setError(err.message || 'Failed to accept invitation');
69
+ } finally {
70
+ setSubmitting(false);
71
+ }
72
+ }, [token, email, password, confirmPassword, onSuccess]);
73
+
74
+ if (validating) {
75
+ return (
76
+ <div className="auth-page">
77
+ <div className="auth-card">
78
+ <div className="auth-header">
79
+ <h1 className="auth-title">Validating Invitation...</h1>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ if (!token || !email || !valid) {
87
+ return (
88
+ <div className="auth-page">
89
+ <div className="auth-card">
90
+ <div className="auth-header">
91
+ <h1 className="auth-title">Invalid Invitation</h1>
92
+ </div>
93
+ <div className="auth-success-message">
94
+ <p>This invitation link is invalid or has expired.</p>
95
+ <Link to="/login" className="auth-back-link">Go to login</Link>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ return (
103
+ <div className="auth-page">
104
+ <div className="auth-card">
105
+ <div className="auth-header">
106
+ <h1 className="auth-title">Accept Invitation</h1>
107
+ <p className="auth-subtitle">Set your password for {email}</p>
108
+ </div>
109
+
110
+ <form className="auth-form" onSubmit={handleSubmit} noValidate>
111
+ {error && <div className="auth-error-banner">{error}</div>}
112
+
113
+ <div className="auth-field">
114
+ <label className="auth-label" htmlFor="invite-password">Password</label>
115
+ <input
116
+ id="invite-password"
117
+ className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
118
+ type="password"
119
+ value={password}
120
+ onChange={e => setPassword(e.target.value)}
121
+ placeholder="Min 8 characters"
122
+ autoComplete="new-password"
123
+ autoFocus
124
+ disabled={submitting}
125
+ />
126
+ </div>
127
+
128
+ <div className="auth-field">
129
+ <label className="auth-label" htmlFor="invite-confirm-password">Confirm Password</label>
130
+ <input
131
+ id="invite-confirm-password"
132
+ className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
133
+ type="password"
134
+ value={confirmPassword}
135
+ onChange={e => setConfirmPassword(e.target.value)}
136
+ placeholder="Repeat password"
137
+ autoComplete="new-password"
138
+ disabled={submitting}
139
+ />
140
+ {fieldError && <span className="auth-field-error">{fieldError}</span>}
141
+ </div>
142
+
143
+ <button
144
+ className="auth-submit"
145
+ type="submit"
146
+ disabled={submitting}
147
+ >
148
+ {submitting ? 'Creating account...' : 'Create Account'}
149
+ </button>
150
+ </form>
151
+
152
+ <p className="auth-login-link">
153
+ <Link to="/login">Already have an account? Sign in</Link>
154
+ </p>
155
+
156
+ <button
157
+ className="auth-theme-toggle"
158
+ onClick={toggleTheme}
159
+ type="button"
160
+ title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
161
+ >
162
+ {theme === 'dark' ? 'light' : 'dark'} theme
163
+ </button>
164
+ </div>
165
+ </div>
166
+ );
167
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Forgot Password Page — user enters email to request a password reset link.
3
+ * Always shows success message to prevent user enumeration.
4
+ */
5
+
6
+ import React, { useState, useCallback } from 'react';
7
+ import { Link } from 'react-router-dom';
8
+ import { useTheme } from '../hooks/useTheme';
9
+ import { apiClient } from '../api/client';
10
+ import { validateEmail } from '../utils/auth_validation';
11
+
12
+ export function ForgotPasswordPage() {
13
+ const { theme, toggleTheme } = useTheme();
14
+ const [email, setEmail] = useState('');
15
+ const [error, setError] = useState('');
16
+ const [submitting, setSubmitting] = useState(false);
17
+ const [sent, setSent] = useState(false);
18
+
19
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
20
+ e.preventDefault();
21
+ setError('');
22
+
23
+ const emailErr = validateEmail(email);
24
+ if (emailErr) {
25
+ setError(emailErr);
26
+ return;
27
+ }
28
+
29
+ setSubmitting(true);
30
+ try {
31
+ await apiClient.forgotPassword(email.trim());
32
+ setSent(true);
33
+ } catch (err: any) {
34
+ setError(err.message || 'Failed to send reset email');
35
+ } finally {
36
+ setSubmitting(false);
37
+ }
38
+ }, [email]);
39
+
40
+ return (
41
+ <div className="auth-page">
42
+ <div className="auth-card">
43
+ <div className="auth-header">
44
+ <h1 className="auth-title">Reset Password</h1>
45
+ <p className="auth-subtitle">Enter your email to receive a reset link</p>
46
+ </div>
47
+
48
+ {sent ? (
49
+ <div className="auth-success-message">
50
+ <p>If an account with that email exists, a reset link has been sent.</p>
51
+ <p>Check your inbox and follow the link to reset your password.</p>
52
+ <Link to="/login" className="auth-back-link">Back to login</Link>
53
+ </div>
54
+ ) : (
55
+ <>
56
+ <form className="auth-form" onSubmit={handleSubmit} noValidate>
57
+ {error && <div className="auth-error-banner">{error}</div>}
58
+
59
+ <div className="auth-field">
60
+ <label className="auth-label" htmlFor="forgot-email">Email</label>
61
+ <input
62
+ id="forgot-email"
63
+ className={`auth-input${error ? ' auth-input-error' : ''}`}
64
+ type="email"
65
+ value={email}
66
+ onChange={e => setEmail(e.target.value)}
67
+ placeholder="user@example.com"
68
+ autoComplete="email"
69
+ autoFocus
70
+ disabled={submitting}
71
+ />
72
+ </div>
73
+
74
+ <button
75
+ className="auth-submit"
76
+ type="submit"
77
+ disabled={submitting}
78
+ >
79
+ {submitting ? 'Sending...' : 'Send Reset Link'}
80
+ </button>
81
+ </form>
82
+
83
+ <p className="auth-login-link">
84
+ Remember your password? <Link to="/login">Sign in</Link>
85
+ </p>
86
+ </>
87
+ )}
88
+
89
+ <button
90
+ className="auth-theme-toggle"
91
+ onClick={toggleTheme}
92
+ type="button"
93
+ title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
94
+ >
95
+ {theme === 'dark' ? 'light' : 'dark'} theme
96
+ </button>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Registration Page — first-user-is-admin flow.
3
+ * Only accessible when no users exist in the system.
4
+ * Email + password form with inline validation.
5
+ * On success, redirects to the main app.
6
+ */
7
+
8
+ import React, { useState, useCallback } from 'react';
9
+ import { Link } from 'react-router-dom';
10
+ import { useTheme } from '../hooks/useTheme';
11
+ import { apiClient } from '../api/client';
12
+ import { validateEmail, validatePassword } from '../utils/auth_validation';
13
+
14
+ interface RegisterPageProps {
15
+ onSuccess: () => void;
16
+ }
17
+
18
+ interface FormErrors {
19
+ email?: string;
20
+ password?: string;
21
+ general?: string;
22
+ }
23
+
24
+ export function RegisterPage({ onSuccess }: RegisterPageProps) {
25
+ const { theme, toggleTheme } = useTheme();
26
+ const [email, setEmail] = useState('');
27
+ const [password, setPassword] = useState('');
28
+ const [errors, setErrors] = useState<FormErrors>({});
29
+ const [submitting, setSubmitting] = useState(false);
30
+
31
+ const validate = useCallback((): FormErrors => {
32
+ const errs: FormErrors = {};
33
+ const emailErr = validateEmail(email);
34
+ if (emailErr) errs.email = emailErr;
35
+ const passErr = validatePassword(password);
36
+ if (passErr) errs.password = passErr;
37
+ return errs;
38
+ }, [email, password]);
39
+
40
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
41
+ e.preventDefault();
42
+ const errs = validate();
43
+ setErrors(errs);
44
+ if (Object.keys(errs).length > 0) return;
45
+
46
+ setSubmitting(true);
47
+ setErrors({});
48
+
49
+ try {
50
+ await apiClient.register(email.trim(), password);
51
+ onSuccess();
52
+ } catch (err: any) {
53
+ setErrors({ general: err.message || 'Registration failed' });
54
+ } finally {
55
+ setSubmitting(false);
56
+ }
57
+ }, [email, password, validate, onSuccess]);
58
+
59
+ const handleEmailChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
60
+ setEmail(e.target.value);
61
+ if (errors.email) setErrors(prev => ({ ...prev, email: undefined }));
62
+ }, [errors.email]);
63
+
64
+ const handlePasswordChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
65
+ setPassword(e.target.value);
66
+ if (errors.password) setErrors(prev => ({ ...prev, password: undefined }));
67
+ }, [errors.password]);
68
+
69
+ return (
70
+ <div className="auth-page">
71
+ <div className="auth-card">
72
+ <div className="auth-header">
73
+ <h1 className="auth-title">Create Admin Account</h1>
74
+ <p className="auth-subtitle">Set up the first user for this system</p>
75
+ </div>
76
+
77
+ <form className="auth-form" onSubmit={handleSubmit} noValidate>
78
+ {errors.general && (
79
+ <div className="auth-error-banner">{errors.general}</div>
80
+ )}
81
+
82
+ <div className="auth-field">
83
+ <label className="auth-label" htmlFor="register-email">Email</label>
84
+ <input
85
+ id="register-email"
86
+ className={`auth-input${errors.email ? ' auth-input-error' : ''}`}
87
+ type="email"
88
+ value={email}
89
+ onChange={handleEmailChange}
90
+ placeholder="admin@example.com"
91
+ autoComplete="email"
92
+ autoFocus
93
+ disabled={submitting}
94
+ />
95
+ {errors.email && <span className="auth-field-error">{errors.email}</span>}
96
+ </div>
97
+
98
+ <div className="auth-field">
99
+ <label className="auth-label" htmlFor="register-password">Password</label>
100
+ <input
101
+ id="register-password"
102
+ className={`auth-input${errors.password ? ' auth-input-error' : ''}`}
103
+ type="password"
104
+ value={password}
105
+ onChange={handlePasswordChange}
106
+ placeholder="Min 8 characters"
107
+ autoComplete="new-password"
108
+ disabled={submitting}
109
+ />
110
+ {errors.password && <span className="auth-field-error">{errors.password}</span>}
111
+ </div>
112
+
113
+ <button
114
+ className="auth-submit"
115
+ type="submit"
116
+ disabled={submitting}
117
+ >
118
+ {submitting ? 'Creating account...' : 'Create Account'}
119
+ </button>
120
+ </form>
121
+
122
+ <p className="auth-login-link">
123
+ Already have an account? <Link to="/login">Sign in</Link>
124
+ </p>
125
+
126
+ <button
127
+ className="auth-theme-toggle"
128
+ onClick={toggleTheme}
129
+ type="button"
130
+ title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
131
+ >
132
+ {theme === 'dark' ? 'light' : 'dark'} theme
133
+ </button>
134
+ </div>
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Reset Password Page — user enters new password after clicking reset link.
3
+ * Token is extracted from URL query parameter.
4
+ */
5
+
6
+ import React, { useState, useCallback } from 'react';
7
+ import { Link, useSearchParams } from 'react-router-dom';
8
+ import { useTheme } from '../hooks/useTheme';
9
+ import { apiClient } from '../api/client';
10
+ import { validatePassword } from '../utils/auth_validation';
11
+
12
+ export function ResetPasswordPage() {
13
+ const { theme, toggleTheme } = useTheme();
14
+ const [searchParams] = useSearchParams();
15
+ const token = searchParams.get('token') || '';
16
+
17
+ const [password, setPassword] = useState('');
18
+ const [confirmPassword, setConfirmPassword] = useState('');
19
+ const [error, setError] = useState('');
20
+ const [fieldError, setFieldError] = useState('');
21
+ const [submitting, setSubmitting] = useState(false);
22
+ const [success, setSuccess] = useState(false);
23
+
24
+ const handleSubmit = useCallback(async (e: React.FormEvent) => {
25
+ e.preventDefault();
26
+ setError('');
27
+ setFieldError('');
28
+
29
+ if (!token) {
30
+ setError('Missing reset token. Please use the link from your email.');
31
+ return;
32
+ }
33
+
34
+ const passErr = validatePassword(password);
35
+ if (passErr) {
36
+ setFieldError(passErr);
37
+ return;
38
+ }
39
+
40
+ if (password !== confirmPassword) {
41
+ setFieldError('Passwords do not match');
42
+ return;
43
+ }
44
+
45
+ setSubmitting(true);
46
+ try {
47
+ await apiClient.resetPassword(token, password);
48
+ setSuccess(true);
49
+ } catch (err: any) {
50
+ setError(err.message || 'Failed to reset password');
51
+ } finally {
52
+ setSubmitting(false);
53
+ }
54
+ }, [token, password, confirmPassword]);
55
+
56
+ if (!token) {
57
+ return (
58
+ <div className="auth-page">
59
+ <div className="auth-card">
60
+ <div className="auth-header">
61
+ <h1 className="auth-title">Invalid Link</h1>
62
+ </div>
63
+ <div className="auth-success-message">
64
+ <p>This reset link is invalid or missing a token.</p>
65
+ <Link to="/forgot-password" className="auth-back-link">Request a new reset link</Link>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ return (
73
+ <div className="auth-page">
74
+ <div className="auth-card">
75
+ <div className="auth-header">
76
+ <h1 className="auth-title">Set New Password</h1>
77
+ <p className="auth-subtitle">Enter your new password below</p>
78
+ </div>
79
+
80
+ {success ? (
81
+ <div className="auth-success-message">
82
+ <p>Your password has been reset successfully.</p>
83
+ <Link to="/login" className="auth-back-link">Sign in with your new password</Link>
84
+ </div>
85
+ ) : (
86
+ <>
87
+ <form className="auth-form" onSubmit={handleSubmit} noValidate>
88
+ {error && <div className="auth-error-banner">{error}</div>}
89
+
90
+ <div className="auth-field">
91
+ <label className="auth-label" htmlFor="reset-password">New Password</label>
92
+ <input
93
+ id="reset-password"
94
+ className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
95
+ type="password"
96
+ value={password}
97
+ onChange={e => setPassword(e.target.value)}
98
+ placeholder="Min 8 characters"
99
+ autoComplete="new-password"
100
+ autoFocus
101
+ disabled={submitting}
102
+ />
103
+ </div>
104
+
105
+ <div className="auth-field">
106
+ <label className="auth-label" htmlFor="reset-confirm-password">Confirm Password</label>
107
+ <input
108
+ id="reset-confirm-password"
109
+ className={`auth-input${fieldError ? ' auth-input-error' : ''}`}
110
+ type="password"
111
+ value={confirmPassword}
112
+ onChange={e => setConfirmPassword(e.target.value)}
113
+ placeholder="Repeat password"
114
+ autoComplete="new-password"
115
+ disabled={submitting}
116
+ />
117
+ {fieldError && <span className="auth-field-error">{fieldError}</span>}
118
+ </div>
119
+
120
+ <button
121
+ className="auth-submit"
122
+ type="submit"
123
+ disabled={submitting}
124
+ >
125
+ {submitting ? 'Resetting...' : 'Reset Password'}
126
+ </button>
127
+ </form>
128
+
129
+ <p className="auth-login-link">
130
+ <Link to="/login">Back to login</Link>
131
+ </p>
132
+ </>
133
+ )}
134
+
135
+ <button
136
+ className="auth-theme-toggle"
137
+ onClick={toggleTheme}
138
+ type="button"
139
+ title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`}
140
+ >
141
+ {theme === 'dark' ? 'light' : 'dark'} theme
142
+ </button>
143
+ </div>
144
+ </div>
145
+ );
146
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { Navigate, Outlet } from 'react-router-dom';
3
+ import { useAuth } from '../hooks/useAuth';
4
+
5
+ export function ProtectedRoute() {
6
+ const { user, loading } = useAuth();
7
+
8
+ if (loading) return null;
9
+ if (!user) return <Navigate to="/login" replace />;
10
+
11
+ return <Outlet />;
12
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { Navigate, useNavigate } from 'react-router-dom';
3
+ import { AcceptInvitationPage } from '../pages/accept_invitation_page';
4
+ import { useAuth } from '../hooks/useAuth';
5
+
6
+ export function AcceptInvitationRoute() {
7
+ const { user, loading } = useAuth();
8
+ const navigate = useNavigate();
9
+
10
+ if (loading) return null;
11
+ if (user) return <Navigate to="/" replace />;
12
+
13
+ return <AcceptInvitationPage onSuccess={() => navigate('/', { replace: true })} />;
14
+ }