alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
669 lines (631 loc) • 16.1 kB
JavaScript
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