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 +2 -2
- package/cli/platform.mjs +1 -1
- package/package.json +2 -2
- package/skills/yad-checks/SKILL.md +2 -0
- package/skills/yad-checks/references/check-gates.md +5 -0
- package/skills/yad-checks/templates/checks/build-test-lint.sh +13 -1
- package/skills/yad-checks/templates/github/yad-checks.yml +2 -0
- package/skills/yad-checks/templates/gitlab/yad-checks.gitlab-ci.yml +2 -0
- package/skills/yad-docs/references/data-mapping.md +22 -0
- package/skills/yad-docs/templates/app/index.html +1 -1
- package/skills/yad-docs/templates/app/public/yadflow-icon.png +0 -0
- package/skills/yad-docs/templates/app/src/App.tsx +0 -8
- package/skills/yad-docs/templates/app/src/components/Canvas/SystemComponent.tsx +2 -2
- package/skills/yad-docs/templates/app/src/components/DetailPanel/RightPanel.tsx +14 -13
- package/skills/yad-docs/templates/app/src/components/Navigation/TopNavBar.tsx +13 -29
- package/skills/yad-docs/templates/app/src/components/Reference/RulesLegendPanel.tsx +16 -11
- package/skills/yad-docs/templates/app/src/components/Sidebar/PathSelector.tsx +6 -3
- package/skills/yad-docs/templates/app/src/components/shared/Tooltip.tsx +3 -2
- package/skills/yad-docs/templates/app/src/components/Auth/LoginPage.tsx +0 -101
- package/skills/yad-docs/templates/app/src/store/useAuthStore.ts +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# [2.
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
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": "^
|
|
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
|
-
|
|
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."
|
|
@@ -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/
|
|
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" />
|
|
Binary file
|
|
@@ -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: '
|
|
56
|
-
minHeight: '
|
|
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
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
</
|
|
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-
|
|
21
|
-
<img src=
|
|
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
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
>
|
|
209
|
-
<
|
|
210
|
-
|
|
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
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
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=
|
|
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
|
-
}));
|