yadflow 2.8.0 → 2.10.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.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,9 @@
1
- # [2.8.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.7.0...v2.8.0) (2026-06-15)
1
+ # [2.10.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.9.0...v2.10.0) (2026-06-15)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * add `yad roster` command to manage the reviewer roster any time ([#64](https://github.com/abdelrahmannasr/yadflow/issues/64)) ([4d78225](https://github.com/abdelrahmannasr/yadflow/commit/4d78225ec25579b50d24d917f217212f4820728f))
6
+ * **checks:** cap jest/vitest test workers in connected-repo CI gates ([#66](https://github.com/abdelrahmannasr/yadflow/issues/66)) ([7a16d51](https://github.com/abdelrahmannasr/yadflow/commit/7a16d51eb135c3240d3e94012f844c8b74210bd9))
7
7
 
8
8
  # [2.2.0](https://github.com/abdelrahmannasr/yadflow/compare/v2.1.0...v2.2.0) (2026-06-14)
9
9
 
package/cli/platform.mjs CHANGED
@@ -89,7 +89,7 @@ export function validateLogin(platform, login) {
89
89
  const r = run('glab', ['api', `users?username=${encodeURIComponent(login)}`]);
90
90
  if (!r.ok) return { ok: false, exists: false, checked: true };
91
91
  let exists = false;
92
- try { exists = Array.isArray(JSON.parse(r.stdout)) && JSON.parse(r.stdout).length > 0; } catch { exists = false; }
92
+ try { exists = Array.isArray(JSON.parse(r.stdout)) && JSON.parse(r.stdout).length > 0; } catch { /* malformed JSON -> exists stays false */ }
93
93
  return { ok: exists, exists, checked: true };
94
94
  }
95
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yadflow",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Yadflow — the gated, team, multi-repo SDLC: author → review → build with a PR-driven review gate and a zero-dependency `yad` CLI (setup, gate, commit, open-pr, ship, repo). A BMAD module + 29 yad-* skills.",
5
5
  "type": "module",
6
6
  "author": "AbdelRahman Nasr",
@@ -56,7 +56,7 @@
56
56
  "devDependencies": {
57
57
  "@eslint/js": "^10.0.1",
58
58
  "@semantic-release/changelog": "^6.0.3",
59
- "eslint": "^9.39.4",
59
+ "eslint": "^10.5.0",
60
60
  "semantic-release": "^25.0.3"
61
61
  }
62
62
  }
@@ -16,6 +16,8 @@ in CI on every PR/MR and must pass before merge (build plan §C). Each is a smal
16
16
  contract upstream, it **FAILS and routes back to the architecture gate**. The shared surface is
17
17
  never widened from inside a code repo (Phase 2 contract representation: delimited block + SHA-256 lock).
18
18
  3. **build/test/lint** — standard quality stage; tests must actually exercise new behavior, not just pass.
19
+ The CI job sets `YAD_TEST_MAX_WORKERS` (default `2`); the gate caps jest/vitest test concurrency at
20
+ that and is a no-op for other runners (see `references/check-gates.md`).
19
21
  4. **verified-commits** — no unverified commits from unverified users: every commit in the range must
20
22
  carry a signature the platform marks **Verified** AND be authored by a known identity
21
23
  (`.sdlc/verified-authors`, generated from the hub roster's `email` fields). Enforced on the
@@ -46,6 +46,11 @@ repo uses. Each reads conventions established by earlier steps — it invents no
46
46
  - Runs `npm run lint`, `npm run build`, `npm test` in order; any non-zero exit fails the gate.
47
47
  - Tests must actually exercise behavior (build plan §C) — an empty or trivially-passing suite does not
48
48
  satisfy the gate's intent.
49
+ - **Test worker cap.** When the CI job sets `YAD_TEST_MAX_WORKERS` (the templates default it to `2`)
50
+ and the repo's `test` script is jest/vitest, the gate forwards `--maxWorkers=<n>` to bound CI
51
+ concurrency. For any other runner (`node --test`, mocha, …) it is a no-op — the flag is never
52
+ passed, so the gate cannot break on an unknown option. Override it per repo via the
53
+ `YAD_TEST_MAX_WORKERS` CI variable, or unset it to remove the cap.
49
54
 
50
55
  ### Canonical `package.json` scripts (Node demo)
51
56
 
@@ -8,7 +8,19 @@ echo "[build/test/lint] lint…"
8
8
  npm run --silent lint
9
9
  echo "[build/test/lint] build…"
10
10
  npm run --silent build
11
+
12
+ # Worker cap: when YAD_TEST_MAX_WORKERS is set AND the repo's test script is jest/vitest (the
13
+ # runners that accept --maxWorkers), forward it to bound CI test concurrency. For any other runner
14
+ # (node --test, mocha, …) it is a deliberate no-op so the gate never breaks on an unknown flag.
15
+ extra=""
16
+ if [ -n "${YAD_TEST_MAX_WORKERS:-}" ]; then
17
+ case "$(npm pkg get scripts.test 2>/dev/null || true)" in
18
+ *jest*|*vitest*) extra="-- --maxWorkers=${YAD_TEST_MAX_WORKERS}" ;;
19
+ esac
20
+ fi
11
21
  echo "[build/test/lint] test…"
12
- npm run --silent test
22
+ # Intentional word-splitting: $extra is either empty or `-- --maxWorkers=N`.
23
+ # shellcheck disable=SC2086
24
+ npm run --silent test $extra
13
25
 
14
26
  echo "PASS [build/test/lint]: lint, build, and tests all green."
@@ -25,6 +25,8 @@ jobs:
25
25
 
26
26
  build-test-lint:
27
27
  runs-on: ubuntu-latest
28
+ env:
29
+ YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
28
30
  steps:
29
31
  - uses: actions/checkout@v4
30
32
  with: { fetch-depth: 0 }
@@ -42,6 +42,8 @@ yad-contract-check:
42
42
  yad-build-test-lint:
43
43
  extends: .sdlc_mr_only
44
44
  needs: []
45
+ variables:
46
+ YAD_TEST_MAX_WORKERS: "2" # cap jest/vitest test workers in CI; ignored by other runners
45
47
  script:
46
48
  - bash checks/build-test-lint.sh
47
49
 
@@ -15,6 +15,28 @@ shell renders whatever these export, as long as it satisfies `src/data/types.ts`
15
15
  | `docSections.ts` | `DOC_SECTIONS: DocSectionConfig[]` | `epic.md`, `architecture.md`, `contract.md`, `ui-design.md`, `test-cases.md` | the ordered doc-section registry (`{ id, title, icon, iconColor, component }`); each section id is referenced from `roles.ts`. |
16
16
  | `referenceData.ts` | the reference tables/payloads the doc-section components render | `contract.md` CONTRACT-SURFACE (authoritative) + `architecture.md` + `test-cases.md` | API reference rows, the status machine, the DB schema, feature flags, error codes, the test plan — the structured data behind the doc sections. |
17
17
 
18
+ ## Canvas layout (`components.ts` `position`)
19
+
20
+ `position` is `{ x, y }` in 0–100 (percent of the canvas). Lay the components out as a **hub-and-spoke
21
+ organized around a central hub into four surrounding zones** so the spokes fan out without crossing,
22
+ rather than scattering nodes:
23
+
24
+ - **Center** — the product hub (the brain).
25
+ - **Top band** — the file ledger the hub owns (state / approvals / contract-lock), spread across one row.
26
+ - **Left** — the code side: each connector with its external target just beyond it (`repos-json → code-repos`).
27
+ - **Right** — the connected tools as a single aligned column: each connector on the inner edge with its
28
+ external tool on the same row just outside it (`design-json → Design Tool`, etc.).
29
+ - **Bottom band** — publish / platform / evidence (docs / platform / trust-log).
30
+
31
+ Layout constraints (the nodes are fixed-size cards, ~116×146px, so spacing is what prevents overlap):
32
+ - Only **~4 rows** fit vertically — keep row centers **≥23% apart**; same-row neighbours **≥18% apart** in x.
33
+ - Keep all nodes inside ~6–94% on each axis so no card clips the canvas edge (tool column ≤ ~88% x,
34
+ bottom band ≤ ~80% y).
35
+ - Keep `label`s short (e.g. `Git Platform`, not `Platform (GitHub/GitLab)`) — a long label widens the
36
+ card and breaks the spacing.
37
+ - The layout is **deterministic**: assign zones by role (ledger / code / tools / platform+evidence) and
38
+ order within a zone by stable id, so an unchanged architecture regenerates byte-identically.
39
+
18
40
  ## Section sources (the doc sections + their artifact)
19
41
 
20
42
  | Doc section(s) | Artifact source |
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
5
+ <link rel="icon" type="image/png" href="/yadflow-icon.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Booking Flow Visualizer</title>
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
@@ -12,10 +12,8 @@ import { SystemLogsTerminal } from './components/Logs/SystemLogsTerminal';
12
12
  import { SubPathDetailPage } from './pages/SubPathDetailPage';
13
13
  import { RoleSelectPage } from './pages/RoleSelectPage';
14
14
  import { StakeholderDocPage } from './pages/StakeholderDocPage';
15
- import { LoginPage } from './components/Auth/LoginPage';
16
15
  import { usePlayback } from './hooks/usePlayback';
17
16
  import { useFlowStore } from './store/useFlowStore';
18
- import { useAuthStore } from './store/useAuthStore';
19
17
 
20
18
  function Dashboard() {
21
19
  usePlayback();
@@ -74,12 +72,6 @@ function Dashboard() {
74
72
  }
75
73
 
76
74
  function App() {
77
- const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
78
-
79
- if (!isAuthenticated) {
80
- return <LoginPage />;
81
- }
82
-
83
75
  return (
84
76
  <div className="flex flex-col h-screen w-screen overflow-hidden" style={{ background: 'var(--color-bg-primary)' }}>
85
77
  <TopNavBar />
@@ -52,8 +52,8 @@ export const SystemComponent: React.FC<SystemComponentProps> = React.memo(
52
52
  : 'rgba(47, 41, 56, 0.4)',
53
53
  backdropFilter: 'blur(12px)',
54
54
  ...glowStyle,
55
- minWidth: '130px',
56
- minHeight: '140px',
55
+ minWidth: '116px',
56
+ minHeight: '120px',
57
57
  }}
58
58
  animate={
59
59
  isReceiving
@@ -6,6 +6,7 @@ import { TriggerEventCard } from './TriggerEventCard';
6
6
  import { RequestPayloadPreview } from './RequestPayloadPreview';
7
7
  import { HandlerLogicSnippet } from './HandlerLogicSnippet';
8
8
  import { Icon } from '../shared/Icon';
9
+ import { Tooltip } from '../shared/Tooltip';
9
10
 
10
11
  export function RightPanel() {
11
12
  const getCurrentStep = useFlowStore((s) => s.getCurrentStep);
@@ -68,19 +69,19 @@ export function RightPanel() {
68
69
  <Icon name="open_in_new" size={18} />
69
70
  View Full Path Details
70
71
  </button>
71
- <button
72
- onClick={() => console.log('Debug step:', step)}
73
- className="w-full py-2.5 rounded-lg text-slate-300 text-sm font-medium border transition-colors flex items-center justify-center gap-2"
74
- style={{
75
- background: 'rgba(255,255,255,0.05)',
76
- borderColor: 'rgba(255,255,255,0.05)',
77
- }}
78
- onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.1)'}
79
- onMouseLeave={(e) => e.currentTarget.style.background = 'rgba(255,255,255,0.05)'}
80
- >
81
- <Icon name="bug_report" size={18} />
82
- Debug Step
83
- </button>
72
+ <Tooltip content="Coming soon" className="w-full">
73
+ <button
74
+ disabled
75
+ className="w-full py-2.5 rounded-lg text-slate-300 text-sm font-medium border flex items-center justify-center gap-2 opacity-50 cursor-not-allowed"
76
+ style={{
77
+ background: 'rgba(255,255,255,0.05)',
78
+ borderColor: 'rgba(255,255,255,0.05)',
79
+ }}
80
+ >
81
+ <Icon name="bug_report" size={18} />
82
+ Debug Step
83
+ </button>
84
+ </Tooltip>
84
85
  </div>
85
86
  )}
86
87
  </aside>
@@ -1,13 +1,12 @@
1
1
  import { useNavigate } from 'react-router-dom';
2
2
  import { Icon } from '../shared/Icon';
3
+ import { Tooltip } from '../shared/Tooltip';
3
4
  import { useFlowStore } from '../../store/useFlowStore';
4
- import { useAuthStore } from '../../store/useAuthStore';
5
5
 
6
6
  export function TopNavBar() {
7
7
  const navigate = useNavigate();
8
8
  const toggleReferencePanel = useFlowStore((s) => s.toggleReferencePanel);
9
9
  const toggleCommandPalette = useFlowStore((s) => s.toggleCommandPalette);
10
- const logout = useAuthStore((s) => s.logout);
11
10
 
12
11
  return (
13
12
  <header className="flex-none flex items-center justify-between whitespace-nowrap border-b px-6 py-3 z-20"
@@ -17,8 +16,9 @@ export function TopNavBar() {
17
16
  }}
18
17
  >
19
18
  <div className="flex items-center gap-8">
20
- <div className="flex items-center gap-3 text-white">
21
- <img src="/logo.svg" alt="Logo" className="h-8" />
19
+ <div className="flex items-center gap-2 text-white">
20
+ <img src={`${import.meta.env.BASE_URL}yadflow-icon.png`} alt="yadflow" className="h-9 w-9 object-contain" />
21
+ <span className="text-lg font-bold font-display text-white tracking-tight">yadflow</span>
22
22
  </div>
23
23
  <button
24
24
  onClick={toggleCommandPalette}
@@ -58,31 +58,15 @@ export function TopNavBar() {
58
58
  <Icon name="description" size={18} className="mr-2" />
59
59
  Docs
60
60
  </button>
61
- <button
62
- className="flex items-center justify-center px-4 py-2 rounded-full text-slate-300 text-sm font-medium transition-colors"
63
- onMouseEnter={(e) => e.currentTarget.style.background = 'var(--color-surface-highlight)'}
64
- onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
65
- >
66
- <Icon name="settings" size={18} className="mr-2" />
67
- Settings
68
- </button>
69
- </div>
70
- <div className="h-6 w-px mx-2" style={{ background: 'var(--color-surface-highlight)' }} />
71
- <div className="flex items-center gap-3">
72
- <div className="text-right hidden sm:block">
73
- <p className="text-sm font-medium text-white">AbdelRahman Nasr</p>
74
- <p className="text-xs text-slate-400">Admin</p>
75
- </div>
76
- <button
77
- onClick={logout}
78
- title="Sign out"
79
- className="h-10 w-10 rounded-full ring-2 ring-[#2f2938] flex items-center justify-center cursor-pointer transition-opacity hover:opacity-80"
80
- style={{
81
- background: 'linear-gradient(135deg, var(--color-primary) 0%, #a855f7 100%)',
82
- }}
83
- >
84
- <Icon name="logout" size={20} className="text-white" />
85
- </button>
61
+ <Tooltip content="Coming soon">
62
+ <button
63
+ disabled
64
+ className="flex items-center justify-center px-4 py-2 rounded-full text-slate-300 text-sm font-medium opacity-50 cursor-not-allowed"
65
+ >
66
+ <Icon name="settings" size={18} className="mr-2" />
67
+ Settings
68
+ </button>
69
+ </Tooltip>
86
70
  </div>
87
71
  </div>
88
72
  </header>
@@ -1,6 +1,7 @@
1
1
  import { motion, AnimatePresence } from 'framer-motion';
2
2
  import { useFlowStore } from '../../store/useFlowStore';
3
3
  import { Icon } from '../shared/Icon';
4
+ import { Tooltip } from '../shared/Tooltip';
4
5
  import { MESSAGE_COLORS } from '../../data/types';
5
6
  import { MESSAGE_TYPE_ICONS } from '../../utils/iconMap';
6
7
  import { StakeholderToggle } from './StakeholderToggle';
@@ -197,17 +198,21 @@ export function RulesLegendPanel() {
197
198
  background: 'var(--color-surface-dark)',
198
199
  }}
199
200
  >
200
- <button className="flex-1 py-3 px-4 rounded-lg text-white text-sm font-bold transition-colors flex items-center justify-center gap-2"
201
- style={{ background: 'var(--color-primary)' }}
202
- >
203
- <Icon name="download" size={18} />
204
- Export Rules PDF
205
- </button>
206
- <button className="p-3 rounded-lg text-slate-300 transition-colors"
207
- style={{ background: 'rgba(255,255,255,0.05)' }}
208
- >
209
- <Icon name="settings" size={20} />
210
- </button>
201
+ <Tooltip content="Coming soon" className="flex-1">
202
+ <button disabled className="w-full py-3 px-4 rounded-lg text-white text-sm font-bold flex items-center justify-center gap-2 opacity-50 cursor-not-allowed"
203
+ style={{ background: 'var(--color-primary)' }}
204
+ >
205
+ <Icon name="download" size={18} />
206
+ Export Rules PDF
207
+ </button>
208
+ </Tooltip>
209
+ <Tooltip content="Coming soon">
210
+ <button disabled className="p-3 rounded-lg text-slate-300 opacity-50 cursor-not-allowed"
211
+ style={{ background: 'rgba(255,255,255,0.05)' }}
212
+ >
213
+ <Icon name="settings" size={20} />
214
+ </button>
215
+ </Tooltip>
211
216
  </div>
212
217
  </motion.div>
213
218
  </>
@@ -5,6 +5,7 @@ import { useFlowStore } from '../../store/useFlowStore';
5
5
  import { PATHS } from '../../data/paths';
6
6
  import type { PathCategory } from '../../data/types';
7
7
  import { Icon } from '../shared/Icon';
8
+ import { Tooltip } from '../shared/Tooltip';
8
9
  import { CATEGORY_ICONS } from '../../utils/iconMap';
9
10
 
10
11
  const CATEGORY_LABELS: Record<PathCategory, string> = {
@@ -51,9 +52,11 @@ export const PathSelector = () => {
51
52
  <h3 className="text-slate-100 text-sm font-bold font-display uppercase tracking-wider">
52
53
  Path Selection
53
54
  </h3>
54
- <button className="text-xs font-medium" style={{ color: 'var(--color-primary)' }}>
55
- View All
56
- </button>
55
+ <Tooltip content="Coming soon">
56
+ <button disabled className="text-xs font-medium opacity-50 cursor-not-allowed" style={{ color: 'var(--color-primary)' }}>
57
+ View All
58
+ </button>
59
+ </Tooltip>
57
60
  </div>
58
61
 
59
62
  {/* Search */}
@@ -4,14 +4,15 @@ import { motion, AnimatePresence } from "framer-motion";
4
4
  interface TooltipProps {
5
5
  content: string;
6
6
  children: React.ReactNode;
7
+ className?: string;
7
8
  }
8
9
 
9
- export const Tooltip: React.FC<TooltipProps> = ({ content, children }) => {
10
+ export const Tooltip: React.FC<TooltipProps> = ({ content, children, className = "" }) => {
10
11
  const [show, setShow] = useState(false);
11
12
 
12
13
  return (
13
14
  <div
14
- className="relative inline-block"
15
+ className={`relative inline-block ${className}`}
15
16
  onMouseEnter={() => setShow(true)}
16
17
  onMouseLeave={() => setShow(false)}
17
18
  onFocus={() => setShow(true)}
@@ -1,101 +0,0 @@
1
- import { useState } from 'react';
2
- import { useAuthStore } from '../../store/useAuthStore';
3
-
4
- export function LoginPage() {
5
- const [username, setUsername] = useState('');
6
- const [password, setPassword] = useState('');
7
- const { login, error } = useAuthStore();
8
-
9
- const handleSubmit = (e: React.FormEvent) => {
10
- e.preventDefault();
11
- login(username, password);
12
- };
13
-
14
- return (
15
- <div
16
- className="h-screen w-screen flex items-center justify-center"
17
- style={{ background: 'var(--color-bg-primary)' }}
18
- >
19
- <div
20
- className="w-full max-w-md rounded-2xl p-10 shadow-2xl"
21
- style={{
22
- background: 'var(--color-bg-secondary)',
23
- border: '1px solid var(--color-border-default)',
24
- }}
25
- >
26
- {/* Logo */}
27
- <div className="flex justify-center mb-6">
28
- <img src="/logo.svg" alt="Logo" className="h-14" />
29
- </div>
30
-
31
- {/* Title */}
32
- <h1 className="text-center text-xl font-bold text-white mb-1">
33
- Booking Flow Documentation
34
- </h1>
35
- <p className="text-center text-sm mb-8" style={{ color: 'var(--color-text-muted)' }}>
36
- Sign in to access the documentation
37
- </p>
38
-
39
- <form onSubmit={handleSubmit} className="space-y-5">
40
- {/* Username */}
41
- <div>
42
- <label className="block text-sm font-semibold text-white mb-2">
43
- Username
44
- </label>
45
- <input
46
- type="text"
47
- value={username}
48
- onChange={(e) => setUsername(e.target.value)}
49
- placeholder="Enter your username"
50
- className="w-full px-4 py-3 rounded-lg text-sm text-white placeholder-slate-500 border outline-none transition-colors focus:border-[var(--color-primary)]"
51
- style={{
52
- background: 'var(--color-surface-highlight)',
53
- borderColor: 'var(--color-border-default)',
54
- }}
55
- autoFocus
56
- />
57
- </div>
58
-
59
- {/* Password */}
60
- <div>
61
- <label className="block text-sm font-semibold text-white mb-2">
62
- Password
63
- </label>
64
- <input
65
- type="password"
66
- value={password}
67
- onChange={(e) => setPassword(e.target.value)}
68
- placeholder="Enter your password"
69
- className="w-full px-4 py-3 rounded-lg text-sm text-white placeholder-slate-500 border outline-none transition-colors focus:border-[var(--color-primary)]"
70
- style={{
71
- background: 'var(--color-surface-highlight)',
72
- borderColor: 'var(--color-border-default)',
73
- }}
74
- />
75
- </div>
76
-
77
- {/* Error */}
78
- {error && (
79
- <p className="text-sm text-red-400 text-center">{error}</p>
80
- )}
81
-
82
- {/* Submit */}
83
- <button
84
- type="submit"
85
- className="w-full py-3 rounded-xl text-white font-semibold text-sm transition-all hover:opacity-90 cursor-pointer"
86
- style={{
87
- background: 'var(--color-primary)',
88
- boxShadow: '0 4px 20px rgba(97, 22, 218, 0.4)',
89
- }}
90
- >
91
- Sign In
92
- </button>
93
- </form>
94
-
95
- <p className="text-center text-xs mt-6" style={{ color: 'var(--color-text-muted)' }}>
96
- Booking flow documentation portal
97
- </p>
98
- </div>
99
- </div>
100
- );
101
- }
@@ -1,42 +0,0 @@
1
- import { create } from 'zustand';
2
-
3
- // Login gate — presentational ONLY, never a security control (real access control is the
4
- // repo / Pages ACL). `yad docs` sets DOCS_REQUIRE_LOGIN to `false` by default for public docs;
5
- // teams publishing to a private Pages site can flip it to `true` (login_gate: true) and set
6
- // credentials via the Vite env vars VITE_DOCS_USER / VITE_DOCS_PASS at build time.
7
- const DOCS_REQUIRE_LOGIN = false;
8
- const CREDENTIALS = {
9
- username: import.meta.env.VITE_DOCS_USER ?? 'docs',
10
- password: import.meta.env.VITE_DOCS_PASS ?? 'docs',
11
- };
12
-
13
- interface AuthStore {
14
- isAuthenticated: boolean;
15
- username: string | null;
16
- error: string | null;
17
- login: (username: string, password: string) => boolean;
18
- logout: () => void;
19
- }
20
-
21
- export const useAuthStore = create<AuthStore>((set) => ({
22
- isAuthenticated: !DOCS_REQUIRE_LOGIN || sessionStorage.getItem('auth') === 'true',
23
- username: sessionStorage.getItem('auth_user'),
24
- error: null,
25
-
26
- login: (username, password) => {
27
- if (username === CREDENTIALS.username && password === CREDENTIALS.password) {
28
- sessionStorage.setItem('auth', 'true');
29
- sessionStorage.setItem('auth_user', username);
30
- set({ isAuthenticated: true, username, error: null });
31
- return true;
32
- }
33
- set({ error: 'Invalid username or password' });
34
- return false;
35
- },
36
-
37
- logout: () => {
38
- sessionStorage.removeItem('auth');
39
- sessionStorage.removeItem('auth_user');
40
- set({ isAuthenticated: false, username: null, error: null });
41
- },
42
- }));