UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

669 lines (631 loc) 16.1 kB
import { useCallback, useEffect, useMemo, useState } from "react"; import { useAuth } from "alepha/react/auth"; import { useRouter } from "alepha/react/router"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; //#region ../../src/react/intro/components/GettingStartedAdminSlide.tsx /** * Hook that provides the admin slide content. * Content changes based on user status and admin permissions. * Returns undefined if admin routes are not configured. */ const useAdminSlide = () => { const { user, has } = useAuth(); const router = useRouter(); if (!router.pages.find((it) => it.name === "adminLayout")) return; const adminAnchorProps = router.anchor(router.path("adminLayout")); if (has("admin:*")) return { text: "You're in control.", sub: "Admin access granted.", steps: [{ num: "✓", text: "You have admin privileges" }, { num: "→", text: /* @__PURE__ */ jsxs(Fragment, { children: [ "Go to ", /* @__PURE__ */ jsx("a", { ...adminAnchorProps, children: "/admin" }), " to manage your application" ] }) }] }; if (user) return { text: "Take the wheel.", sub: "Become admin in two steps.", steps: [{ num: "1", text: /* @__PURE__ */ jsxs(Fragment, { children: [ "Add your email to ", /* @__PURE__ */ jsx("code", { children: "adminEmails" }), " in", " ", /* @__PURE__ */ jsx("code", { children: "AppSecurity.ts" }) ] }) }, { num: "2", text: /* @__PURE__ */ jsxs(Fragment, { children: ["Go to ", /* @__PURE__ */ jsx("a", { ...adminAnchorProps, children: "/admin" })] }) }] }; return { text: "Take the wheel.", sub: "Become admin in three steps.", steps: [ { num: "1", text: /* @__PURE__ */ jsxs(Fragment, { children: [ "Add your email to ", /* @__PURE__ */ jsx("code", { children: "adminEmails" }), " in", " ", /* @__PURE__ */ jsx("code", { children: "AppSecurity.ts" }) ] }) }, { num: "2", text: "Create a user account with that email" }, { num: "3", text: /* @__PURE__ */ jsxs(Fragment, { children: ["Go to ", /* @__PURE__ */ jsx("a", { ...adminAnchorProps, children: "/admin" })] }) } ] }; }; //#endregion //#region ../../src/react/intro/components/GettingStartedAuthSlide.tsx /** * Hook that provides the auth slide content. * Content changes based on whether user is logged in. * Returns undefined if auth routes are not configured. */ const useAuthSlide = () => { const { user, logout } = useAuth(); const router = useRouter(); if (!router.pages.find((it) => it.name === "authLayout")) return; if (user) return { text: "Welcome back!", sub: `You're signed in as ${user.email || user.username || "user"}.`, steps: [{ num: "✓", text: "Authentication is working correctly" }, { num: "→", text: /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("a", { href: "#", onClick: (e) => { e.preventDefault(); logout(); }, children: "Sign out" }), " ", "to test the login flow" ] }) }] }; return { text: "Who are you?", sub: "Create your first account.", steps: [{ num: "1", text: /* @__PURE__ */ jsxs(Fragment, { children: ["Sign up at ", /* @__PURE__ */ jsx("a", { ...router.anchor(router.path("login")), children: "/auth/login" })] }) }, { num: "2", text: /* @__PURE__ */ jsxs(Fragment, { children: ["Customize in ", /* @__PURE__ */ jsx("code", { children: "src/api/AppSecurity.ts" })] }) }] }; }; //#endregion //#region ../../src/react/intro/components/GettingStartedDevtoolsSlide.tsx /** * Hook that provides the devtools slide content. * Only shown when @alepha/devtools is installed and enabled. * Returns undefined if devtools are not available. */ const useDevtoolsSlide = () => { if (!import.meta.env?.VITE_ALEPHA_DEVTOOLS) return; return { text: "Inspect everything.", sub: "DevTools are built in.", steps: [{ num: "→", text: /* @__PURE__ */ jsxs(Fragment, { children: [ "Open", " ", /* @__PURE__ */ jsx("a", { href: "/__devtools/", target: "_blank", rel: "noopener noreferrer", children: "/__devtools" }), " ", "to explore your app" ] }) }, { num: "✓", text: "Browse entities, logs, configuration and dependencies" }] }; }; //#endregion //#region ../../src/react/intro/components/GettingStarted.tsx const defaultFirstSlide = { text: "Let's begin.", sub: "Every story starts with a blank page.", detail: "This one is yours." }; const helpSlide = { text: "Need help?", sub: "We've got you covered.", detail: "Even our AI friends can read the docs.", links: [{ label: "alepha.dev", href: "https://alepha.dev" }, { label: "llms.txt", href: "https://alepha.dev/llms.txt" }] }; const formatServerTime = (isoString) => { try { return new Date(isoString).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } catch { return isoString; } }; /** * A welcome component displayed when creating a new Alepha application. */ const GettingStarted = ({ welcome }) => { const [index, setIndex] = useState(0); const [direction, setDirection] = useState("next"); const authSlide = useAuthSlide(); const adminSlide = useAdminSlide(); const devtoolsSlide = useDevtoolsSlide(); const filteredMessages = useMemo(() => { const result = []; if (welcome) result.push({ ...defaultFirstSlide, detail: `Server time: ${formatServerTime(welcome.serverTime)} - App: ${welcome.appName}` }); else result.push(defaultFirstSlide); if (authSlide) result.push(authSlide); if (adminSlide) result.push(adminSlide); if (devtoolsSlide) result.push(devtoolsSlide); result.push(helpSlide); return result; }, [ welcome, authSlide, adminSlide, devtoolsSlide ]); const current = filteredMessages[index]; const prev = useCallback(() => { setDirection("prev"); setIndex((i) => (i - 1 + filteredMessages.length) % filteredMessages.length); }, [filteredMessages.length]); const next = useCallback(() => { setDirection("next"); setIndex((i) => (i + 1) % filteredMessages.length); }, [filteredMessages.length]); const goTo = useCallback((i) => { setDirection(i > index ? "next" : "prev"); setIndex(i); }, [index]); useEffect(() => { const handleKeyDown = (e) => { if (e.key === "ArrowLeft") prev(); else if (e.key === "ArrowRight") next(); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [prev, next]); return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: styles }), /* @__PURE__ */ jsx("main", { className: "alepha-blank", children: /* @__PURE__ */ jsxs("div", { className: "alepha-blank-content", children: [ /* @__PURE__ */ jsxs("div", { className: "alepha-blank-text-block", children: [ /* @__PURE__ */ jsx("h1", { className: `alepha-blank-message alepha-blank-slide-${direction}`, children: current.text }, index), /* @__PURE__ */ jsx("p", { className: `alepha-blank-sub alepha-blank-slide-${direction}`, children: current.sub }, `sub-${index}`), current.detail && /* @__PURE__ */ jsx("p", { className: `alepha-blank-detail alepha-blank-slide-${direction}`, children: current.detail }, `detail-${index}`), current.steps && /* @__PURE__ */ jsx("div", { className: `alepha-blank-steps alepha-blank-slide-${direction}`, children: current.steps.map((step, i) => /* @__PURE__ */ jsxs("div", { className: "alepha-blank-step", style: { animationDelay: `${.15 + i * .08}s` }, children: [/* @__PURE__ */ jsx("span", { className: "alepha-blank-step-num", children: step.num }), /* @__PURE__ */ jsx("span", { className: "alepha-blank-step-text", children: step.text })] }, i)) }, `steps-${index}`), current.links && /* @__PURE__ */ jsx("div", { className: `alepha-blank-links alepha-blank-slide-${direction}`, children: current.links.map((link, i) => /* @__PURE__ */ jsx("a", { href: link.href, target: link.href.startsWith("http") ? "_blank" : void 0, rel: link.href.startsWith("http") ? "noopener noreferrer" : void 0, style: { animationDelay: `${.1 + i * .05}s` }, children: link.label }, link.href)) }, `links-${index}`) ] }), /* @__PURE__ */ jsxs("div", { className: "alepha-blank-slider", children: [ /* @__PURE__ */ jsx("button", { className: "alepha-blank-nav-btn", onClick: prev, "aria-label": "Previous", children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M9 3L5 7L9 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }), /* @__PURE__ */ jsx("div", { className: "alepha-blank-dots", children: filteredMessages.map((_, i) => /* @__PURE__ */ jsx("button", { className: `alepha-blank-dot ${i === index ? "active" : ""}`, onClick: () => goTo(i), "aria-label": `Go to message ${i + 1}` }, i)) }), /* @__PURE__ */ jsx("button", { className: "alepha-blank-nav-btn", onClick: next, "aria-label": "Next", children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M5 3L9 7L5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) ] }), /* @__PURE__ */ jsxs("div", { className: "alepha-blank-hint", children: [ /* @__PURE__ */ jsx("kbd", { children: "←" }), " ", /* @__PURE__ */ jsx("kbd", { children: "→" }), " to navigate" ] }) ] }) })] }); }; const styles = ` .alepha-blank { display: flex; align-items: center; justify-content: center; min-height: 100svh; background: #fafafa; font-family: system-ui, -apple-system, sans-serif; color: #171717; position: absolute; left: 0; right: 0; bottom: 0; top: 0; width: 100%; } .alepha-blank-content { display: flex; flex-direction: column; align-items: center; text-align: center; max-width: 640px; } .alepha-blank-text-block { min-height: 320px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .alepha-blank-message { font-size: clamp(2rem, 8vw, 3rem); font-weight: 600; letter-spacing: -0.02em; margin: 0; line-height: 1.1; } .alepha-blank-sub { font-size: 1.0625rem; color: #525252; margin: 1rem 0 0; font-weight: 400; line-height: 1.5; } .alepha-blank-detail { font-size: 0.875rem; color: #a3a3a3; margin: 0.5rem 0 0; font-weight: 400; line-height: 1.5; } .alepha-blank-slide-next { animation: alepha-blank-slideNext 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; } .alepha-blank-slide-prev { animation: alepha-blank-slidePrev 0.4s cubic-bezier(0.22, 1, 0.36, 1) both; } @keyframes alepha-blank-slideNext { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } } @keyframes alepha-blank-slidePrev { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } } .alepha-blank-steps { display: flex; flex-direction: column; gap: 0.25rem; margin-top: 1.25rem; text-align: left; } .alepha-blank-step { display: flex; align-items: center; gap: 0.625rem; padding: 0.25rem 0; opacity: 0; animation: alepha-blank-stepIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards; } .alepha-blank-step-num { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; background: #171717; color: #fff; font-size: 0.6875rem; font-weight: 600; flex-shrink: 0; } .alepha-blank-step-text { font-size: 0.8125rem; color: #525252; line-height: 1.5; } .alepha-blank-step-text code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.75rem; background: #f5f5f5; color: #171717; padding: 0.0625rem 0.375rem; border-radius: 3px; border: 1px solid #e5e5e5; } .alepha-blank-step-text a { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #525252; text-decoration: underline; text-underline-offset: 2px; transition: color 0.15s ease; } .alepha-blank-step-text a:hover { color: #171717; } @keyframes alepha-blank-stepIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .alepha-blank-links { display: flex; gap: 1.5rem; margin-top: 1.25rem; } .alepha-blank-links a { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.875rem; color: #525252; text-decoration: none; position: relative; opacity: 0; animation: alepha-blank-fadeIn 0.3s ease-out forwards; transition: color 0.2s ease; } .alepha-blank-links a::after { content: ""; position: absolute; left: 0; right: 0; bottom: -2px; height: 1px; background: #a3a3a3; transform: scaleX(1); transform-origin: left; transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), background 0.2s ease; } .alepha-blank-links a:hover { color: #171717; } .alepha-blank-links a:hover::after { background: #171717; transform: scaleX(1.05); } .alepha-blank-slider { display: flex; align-items: center; gap: 1rem; margin-top: 2.5rem; } .alepha-blank-dots { display: flex; align-items: center; gap: 0.5rem; } .alepha-blank-dot { width: 8px; height: 8px; border-radius: 50%; background: transparent; border: 1.5px solid #d4d4d4; padding: 0; cursor: pointer; transition: all 0.25s cubic-bezier(0.22, 1, 0.36, 1); } .alepha-blank-dot:hover { border-color: #a3a3a3; transform: scale(1.2); } .alepha-blank-dot:focus-visible { outline: 2px solid #737373; outline-offset: 2px; } .alepha-blank-dot.active { background: #737373; border-color: #737373; transform: scale(1.1); } .alepha-blank-nav-btn { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; padding: 0; font-family: inherit; background: transparent; color: #a3a3a3; border: 1.5px solid transparent; border-radius: 8px; cursor: pointer; transition: all 0.2s cubic-bezier(0.22, 1, 0.36, 1); } .alepha-blank-nav-btn:hover { color: #525252; background: #f0f0f0; } .alepha-blank-nav-btn:focus-visible { outline: none; border-color: #737373; color: #525252; } .alepha-blank-nav-btn:active { transform: scale(0.92); background: #e5e5e5; } .alepha-blank-hint { margin-top: 2rem; font-size: 0.75rem; color: #a3a3a3; display: flex; align-items: center; gap: 0.375rem; opacity: 0; animation: alepha-blank-fadeIn 0.5s ease-out 0.5s forwards; } .alepha-blank-hint kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.6875rem; background: #f0f0f0; border: 1px solid #e5e5e5; border-radius: 4px; padding: 0.125rem 0.375rem; box-shadow: 0 1px 0 #d4d4d4; } @keyframes alepha-blank-fadeIn { from { opacity: 0; } to { opacity: 1; } } @media (prefers-reduced-motion: reduce) { .alepha-blank-slide-next, .alepha-blank-slide-prev, .alepha-blank-links a, .alepha-blank-hint, .alepha-blank-step { animation: none; opacity: 1; } .alepha-blank-dot, .alepha-blank-nav-btn, .alepha-blank-links a, .alepha-blank-links a::after { transition: none; } } `; //#endregion export { GettingStarted }; //# sourceMappingURL=index.js.map