Compare commits

..

18 Commits

Author SHA1 Message Date
7ba8649940 Release candidate 2024-06-07 08:03:28 +02:00
485ca28a29 Enable the lang of the term to be undefined 2024-06-07 08:03:13 +02:00
33460afaf2 Release candidate 2024-06-07 02:20:27 +02:00
2421ac2c11 Make the user return the actual language of the terms for accesibility 2024-06-07 02:20:12 +02:00
f0cdb0b80b Fix build 2024-06-06 09:31:27 +02:00
2af953927e Release candidate 2024-06-06 09:14:27 +02:00
dcb9fbd0f7 fixes for the add-story command 2024-06-06 09:13:58 +02:00
5bc1f6479d fmt 2024-06-06 09:13:13 +02:00
f3e4bca468 Add script to copy over the stories 2024-06-06 07:41:01 +02:00
54645f5cff Update storybook setup for portability 2024-06-06 07:28:34 +02:00
a7f3e00821 Remove createKcContextMock from the index 2024-06-06 06:27:28 +02:00
108c281b0c Enable to eject Template.tsx and UserProfileFormFields.tsx 2024-06-06 06:12:05 +02:00
58892cbb56 Change first level build target of bin 2024-06-06 06:11:34 +02:00
dae1053ca8 Consistency with the starter 2024-06-06 06:10:41 +02:00
83a9778c30 Release candidate 2024-06-06 04:36:16 +02:00
c52157bfb9 Remove dependency to powerhooks 2024-06-06 04:35:57 +02:00
62bf846d5f Release candidate 2024-06-06 02:29:25 +02:00
148f7fa316 Rollback unarrowing of the getKcContextMock return type 2024-06-06 02:29:09 +02:00
64 changed files with 506 additions and 391 deletions

View File

@ -7,13 +7,11 @@
background-color: #393939;
}
body.sb-show-preparing-docs > .sb-wrapper {
visibility: hidden;
}
body .sb-preparing-story {
visibility: hidden;
}
</style>

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "10.0.0-rc.31",
"version": "10.0.0-rc.36",
"description": "Create Keycloak themes using React",
"repository": {
"type": "git",

View File

@ -52,7 +52,25 @@ transformCodebase({
fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "bin", "main.js"));
{
let hasBeenPatched = false;
fs.readdirSync(join("dist", "bin")).forEach(fileBasename => {
if (fileBasename !== "main.js" && !fileBasename.endsWith(".index.js")) {
return;
}
const { hasBeenPatched: hasBeenPatched_i } = patchDeprecatedBufferApiUsage(
join("dist", "bin", fileBasename)
);
if (hasBeenPatched_i) {
hasBeenPatched = true;
}
});
assert(hasBeenPatched);
}
fs.chmodSync(
join("dist", "bin", "main.js"),
@ -93,6 +111,10 @@ run(
)}`
);
fs.readdirSync(join("dist", "ncc_out")).forEach(fileBasename =>
assert(!fileBasename.endsWith(".index.js"))
);
transformCodebase({
srcDirPath: join("dist", "ncc_out"),
destDirPath: join("dist", "vite-plugin"),
@ -105,12 +127,30 @@ transformCodebase({
fs.rmSync(join("dist", "ncc_out"), { recursive: true });
patchDeprecatedBufferApiUsage(join("dist", "vite-plugin", "index.js"));
{
const { hasBeenPatched } = patchDeprecatedBufferApiUsage(
join("dist", "vite-plugin", "index.js")
);
assert(hasBeenPatched);
}
fs.rmSync(join("dist", "src"), { recursive: true, force: true });
fs.cpSync("src", join("dist", "src"), { recursive: true });
transformCodebase({
srcDirPath: join("stories"),
destDirPath: join("dist", "stories"),
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
if (!fileRelativePath.endsWith(".stories.tsx")) {
return undefined;
}
return { modifiedSourceCode: sourceCode };
}
});
console.log(chalk.green(`✓ built in ${((Date.now() - startTime) / 1000).toFixed(2)}s`));
function run(command: string) {
@ -127,7 +167,9 @@ function patchDeprecatedBufferApiUsage(filePath: string) {
`var buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(toRead) : new Buffer(toRead);`
);
assert(after !== before, `Patch failed for ${relative(process.cwd(), filePath)}`);
fs.writeFileSync(filePath, Buffer.from(after, "utf8"));
const hasBeenPatched = after !== before;
return { hasBeenPatched };
}

View File

@ -28,7 +28,7 @@ export function startRebuildOnSrcChange() {
console.log(chalk.green("Watching for changes in src/"));
chokidar.watch("src", { ignoreInitial: true }).on("all", async () => {
chokidar.watch(["src", "stories"], { ignoreInitial: true }).on("all", async () => {
await waitForDebounce();
runYarnBuild();

View File

@ -47,9 +47,7 @@ export function createGetKcContextMock<
>(params: {
pageId: PageId;
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
// NOTE: We choose to have a return type less precise than Extract<KcContext, { pageId: PageId }> {
// because we want to be able to use the mock just as the real KcContext.
}): KcContext {
}): Extract<KcContext, { pageId: PageId }> {
const { pageId, overrides } = params;
const kcContextMock = structuredCloneButFunctions(

View File

@ -1,4 +1,3 @@
export type { ExtendKcContext } from "keycloakify/account/KcContext";
export type { PageProps } from "keycloakify/account/pages/PageProps";
export { createGetKcContextMock } from "keycloakify/account/KcContext";
export { createUseI18n } from "keycloakify/account/i18n";

109
src/bin/add-story.ts Normal file
View File

@ -0,0 +1,109 @@
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
import cliSelect from "cli-select";
import {
loginThemePageIds,
accountThemePageIds,
type LoginThemePageId,
type AccountThemePageId,
themeTypes,
type ThemeType
} from "./shared/constants";
import { capitalize } from "tsafe/capitalize";
import * as fs from "fs";
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
import { assert, Equals } from "tsafe/assert";
import { getThemeSrcDirPath } from "./shared/getThemeSrcDirPath";
import type { CliCommandOptions } from "./main";
import { readBuildOptions } from "./shared/buildOptions";
import chalk from "chalk";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildOptions = readBuildOptions({
cliCommandOptions
});
console.log(chalk.cyan("Theme type:"));
const { value: themeType } = await cliSelect<ThemeType>({
values: [...themeTypes]
}).catch(() => {
process.exit(-1);
});
console.log(`${themeType}`);
console.log(chalk.cyan("Select the page you want to create a Storybook for:"));
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
values: (() => {
switch (themeType) {
case "login":
return [...loginThemePageIds];
case "account":
return [...accountThemePageIds];
}
assert<Equals<typeof themeType, never>>(false);
})()
}).catch(() => {
process.exit(-1);
});
console.log(`${pageId}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const componentBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(
/ftl$/,
"stories.tsx"
);
const targetFilePath = pathJoin(
themeSrcDirPath,
themeType,
"pages",
componentBasename
);
if (fs.existsSync(targetFilePath)) {
console.log(`${pathRelative(process.cwd(), targetFilePath)} already exists`);
process.exit(-1);
}
const componentCode = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"stories",
themeType,
"pages",
componentBasename
)
)
.toString("utf8")
.replace('import React from "react";\n', "");
{
const targetDirPath = pathDirname(targetFilePath);
if (!fs.existsSync(targetDirPath)) {
fs.mkdirSync(targetDirPath, { recursive: true });
}
}
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8"));
console.log(
[
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`,
`You can start storybook with ${chalk.bold("yarn storybook")}`
].join("\n")
);
}

View File

@ -39,13 +39,26 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
console.log(chalk.cyan("Select the page you want to customize:"));
const { value: pageId } = await cliSelect<LoginThemePageId | AccountThemePageId>({
const templateValue = "Template.tsx (Layout common to every page)";
const userProfileFormFieldsValue =
"UserProfileFormFields.tsx (Renders the form of the register.ftl, login-update-profile.ftl, update-email.ftl and idp-review-user-profile.ftl)";
const { value: pageIdOrComponent } = await cliSelect<
| LoginThemePageId
| AccountThemePageId
| typeof templateValue
| typeof userProfileFormFieldsValue
>({
values: (() => {
switch (themeType) {
case "login":
return [...loginThemePageIds];
return [
templateValue,
userProfileFormFieldsValue,
...loginThemePageIds
];
case "account":
return [...accountThemePageIds];
return [templateValue, ...accountThemePageIds];
}
assert<Equals<typeof themeType, never>>(false);
})()
@ -53,27 +66,45 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
process.exit(-1);
});
console.log(`${pageId}`);
const componentPageBasename = capitalize(kebabCaseToCamelCase(pageId)).replace(
/ftl$/,
"tsx"
);
console.log(`${pageIdOrComponent}`);
const { themeSrcDirPath } = getThemeSrcDirPath({
reactAppRootDirPath: buildOptions.reactAppRootDirPath
});
const componentBasename = (() => {
if (pageIdOrComponent === templateValue) {
return "Template.tsx";
}
if (pageIdOrComponent === userProfileFormFieldsValue) {
return "UserProfileFormFields.tsx";
}
return capitalize(kebabCaseToCamelCase(pageIdOrComponent)).replace(/ftl$/, "tsx");
})();
const pagesOrDot = (() => {
if (
pageIdOrComponent === templateValue ||
pageIdOrComponent === userProfileFormFieldsValue
) {
return ".";
}
return "pages";
})();
const targetFilePath = pathJoin(
themeSrcDirPath,
themeType,
"pages",
componentPageBasename
pagesOrDot,
componentBasename
);
if (fs.existsSync(targetFilePath)) {
console.log(
`${pageId} is already ejected, ${pathRelative(
`${pageIdOrComponent} is already ejected, ${pathRelative(
process.cwd(),
targetFilePath
)} already exists`
@ -82,6 +113,18 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
process.exit(-1);
}
const componentCode = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
pagesOrDot,
componentBasename
)
)
.toString("utf8");
{
const targetDirPath = pathDirname(targetFilePath);
@ -90,28 +133,66 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
}
}
const componentPageContent = fs
.readFileSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
themeType,
"pages",
componentPageBasename
)
)
.toString("utf8");
fs.writeFileSync(targetFilePath, Buffer.from(componentCode, "utf8"));
fs.writeFileSync(targetFilePath, Buffer.from(componentPageContent, "utf8"));
console.log(
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`
);
edit_KcApp: {
if (
pageIdOrComponent !== templateValue &&
pageIdOrComponent !== userProfileFormFieldsValue
) {
break edit_KcApp;
}
const kcAppTsxPath = pathJoin(themeSrcDirPath, themeType, "KcApp.tsx");
const kcAppTsxCode = fs.readFileSync(kcAppTsxPath).toString("utf8");
const modifiedKcAppTsxCode = (() => {
switch (pageIdOrComponent) {
case templateValue:
return kcAppTsxCode.replace(
`keycloakify/${themeType}/Template`,
"./Template"
);
case userProfileFormFieldsValue:
return kcAppTsxCode.replace(
`keycloakify/login/UserProfileFormFields`,
"./UserProfileFormFields"
);
}
assert<Equals<typeof pageIdOrComponent, never>>(false);
})();
if (kcAppTsxCode === modifiedKcAppTsxCode) {
console.log(
chalk.red(
"Unable to automatically update KcApp.tsx, please update it manually"
)
);
return;
}
fs.writeFileSync(kcAppTsxPath, Buffer.from(modifiedKcAppTsxCode, "utf8"));
console.log(
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), kcAppTsxPath))
)} Updated`
);
return;
}
const userProfileFormFieldComponentName = "UserProfileFormFields";
console.log(
[
``,
`${chalk.green("✓")} ${chalk.bold(
pathJoin(".", pathRelative(process.cwd(), targetFilePath))
)} copy pasted from the Keycloakify source code into your project`,
``,
`You now need to update your page router:`,
``,
@ -127,10 +208,10 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
`// ...`,
``,
chalk.green(
`+const ${componentPageBasename.replace(
`+const ${componentBasename.replace(
/.tsx$/,
""
)} = lazy(() => import("./pages/${componentPageBasename}"));`
)} = lazy(() => import("./pages/${componentBasename}"));`
),
...[
``,
@ -143,11 +224,11 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
` {(() => {`,
` switch (kcContext.pageId) {`,
` // ...`,
`+ case "${pageId}": return (`,
`+ case "${pageIdOrComponent}": return (`,
`+ <Login`,
`+ {...{ kcContext, i18n, classes }}`,
`+ Template={Template}`,
...(!componentPageContent.includes(userProfileFormFieldComponentName)
...(!componentCode.includes(userProfileFormFieldComponentName)
? []
: [
`+ ${userProfileFormFieldComponentName}={${userProfileFormFieldComponentName}}`

View File

@ -162,6 +162,20 @@ program
}
});
program
.command({
name: "add-story",
description: "Add *.stories.tsx file for a specific page to in your Storybook."
})
.task({
skip,
handler: async cliCommandOptions => {
const { command } = await import("./add-story");
await command({ cliCommandOptions });
}
});
program
.command({
name: "initialize-email-theme",

View File

@ -1,10 +1,11 @@
{
"extends": "../../tsproject.json",
"compilerOptions": {
"module": "CommonJS",
"target": "ES5",
"module": "ES2020",
"target": "ES2017",
"esModuleInterop": true,
"lib": ["es2015", "DOM", "ES2019.Object"],
"lib": ["es2015", "ES2019.Object"],
"moduleResolution": "node",
"outDir": "../../dist/bin",
"rootDir": "."
}

View File

@ -47,9 +47,7 @@ export function createGetKcContextMock<
>(params: {
pageId: PageId;
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
// NOTE: We choose to have a return type less precise than Extract<KcContext, { pageId: PageId }> {
// because we want to be able to use the mock just as the real KcContext.
}): KcContext {
}): Extract<KcContext, { pageId: PageId }> {
const { pageId, overrides } = params;
const kcContextMock = structuredCloneButFunctions(

View File

@ -1,5 +1,4 @@
export type { ExtendKcContext, Attribute } from "keycloakify/login/KcContext";
export type { PageProps } from "keycloakify/login/pages/PageProps";
export { createGetKcContextMock } from "keycloakify/login/KcContext";
export { useDownloadTerms } from "keycloakify/login/lib/useDownloadTerms";
export { createUseI18n } from "keycloakify/login/i18n";

View File

@ -7,7 +7,13 @@ import {
import { useOnFistMount } from "keycloakify/tools/useOnFirstMount";
import { KcContext } from "../KcContext";
const obsTermsMarkdown = createStatefulObservable<string | undefined>(() => undefined);
const obs = createStatefulObservable<
| {
termsMarkdown: string;
termsLanguageTag: string | undefined;
}
| undefined
>(() => undefined);
export type KcContextLike = {
pageId: string;
@ -22,26 +28,30 @@ assert<KcContext extends KcContextLike ? true : false>();
/** Allow to avoid bundling the terms and download it on demand*/
export function useDownloadTerms(params: {
kcContext: KcContextLike;
downloadTermMarkdown: (params: { currentLanguageTag: string }) => Promise<string>;
downloadTermsMarkdown: (params: {
currentLanguageTag: string;
}) => Promise<{ termsMarkdown: string; termsLanguageTag: string | undefined }>;
}) {
const { kcContext, downloadTermMarkdown } = params;
const { kcContext, downloadTermsMarkdown } = params;
useOnFistMount(async () => {
if (kcContext.pageId === "terms.ftl" || kcContext.termsAcceptanceRequired) {
const termsMarkdown = await downloadTermMarkdown({
obs.current = await downloadTermsMarkdown({
currentLanguageTag:
kcContext.locale?.currentLanguageTag ?? fallbackLanguageTag
});
obsTermsMarkdown.current = termsMarkdown;
}
});
}
export function useTermsMarkdown() {
useRerenderOnChange(obsTermsMarkdown);
useRerenderOnChange(obs);
const termsMarkdown = obsTermsMarkdown.current;
if (obs.current === undefined) {
return { isDownloadComplete: false as const };
}
return { termsMarkdown };
const { termsMarkdown, termsLanguageTag } = obs.current;
return { isDownloadComplete: true, termsMarkdown, termsLanguageTag };
}

View File

@ -18,15 +18,15 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const { url } = kcContext;
const { termsMarkdown } = useTermsMarkdown();
const { isDownloadComplete, termsMarkdown, termsLanguageTag } = useTermsMarkdown();
if (termsMarkdown === undefined) {
if (!isDownloadComplete) {
return null;
}
return (
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} displayMessage={false} headerNode={msg("termsTitle")}>
<div id="kc-terms-text">
<div id="kc-terms-text" lang={termsLanguageTag}>
<Markdown>{termsMarkdown}</Markdown>
</div>
<form className="form-actions" action={url.loginAction} method="POST">

View File

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { useConst } from "powerhooks/useConst";
import { useConst } from "./useConst";
import { id } from "tsafe/id";
/** Callback is guaranteed to be call only once per component mount event in strict mode */

View File

@ -2,7 +2,7 @@ import React from "react";
import type { KcContext } from "./KcContext";
import KcApp from "./KcApp";
import type { DeepPartial } from "../../dist/tools/DeepPartial";
import { createGetKcContextMock } from "../../dist/account";
import { createGetKcContextMock } from "../../dist/account/KcContext";
import type { KcContextExtraProperties, KcContextExtraPropertiesPerPage } from "./KcContext";
const kcContextExtraProperties: KcContextExtraProperties = {};
@ -33,12 +33,3 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
return { PageStory };
}
export const parameters = {
viewMode: "story",
previewTabs: {
"storybook/docs/panel": {
hidden: true
}
}
};

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "account.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "account.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/account.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "federatedIdentity.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "federatedIdentity.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/federatedIdentity.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,13 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
const pageId = "log.ftl";
import { createPageStory } from "../PageStory";
const { PageStory } = createPageStory({
pageId
pageId: "log.ftl"
});
const meta = {
title: `account/${pageId}`,
title: "account/log.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "password.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "password.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "sessions.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "sessions.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/sessions.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,17 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "totp.ftl";
const { PageStory } = createPageStory({
pageId
});
const { PageStory } = createPageStory({ pageId: "totp.ftl" });
const meta = {
title: `account/${pageId}`,
component: PageStory,
parameters
title: "account/totp.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -13,20 +13,26 @@ export default function KcApp(props: { kcContext: KcContext }) {
useDownloadTerms({
kcContext,
downloadTermMarkdown: async ({ currentLanguageTag }) => {
const resource = (() => {
switch (currentLanguageTag) {
case "fr":
return "/tos/tos_fr.md";
case "es":
return "/tos/tos_es.md";
default:
return "/tos/tos_en.md";
}
})();
downloadTermsMarkdown: async ({ currentLanguageTag }) => {
let termsLanguageTag = currentLanguageTag;
let termsFileName: string;
const response = await fetch(resource);
return response.text();
switch (currentLanguageTag) {
case "fr":
termsFileName = "fr.md";
break;
case "es":
termsFileName = "es.md";
break;
default:
termsFileName = "en.md";
termsLanguageTag = "en";
break;
}
const termsMarkdown = await fetch(`/terms/${termsFileName}`).then(response => response.text());
return { termsMarkdown, termsLanguageTag };
}
});

View File

@ -2,7 +2,7 @@ import React from "react";
import type { KcContext } from "./KcContext";
import KcApp from "./KcApp";
import type { DeepPartial } from "../../dist/tools/DeepPartial";
import { createGetKcContextMock } from "../../dist/login";
import { createGetKcContextMock } from "../../dist/login/KcContext";
import type { KcContextExtraProperties, KcContextExtraPropertiesPerPage } from "./KcContext";
const kcContextExtraProperties: KcContextExtraProperties = {};
@ -33,12 +33,3 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: { pa
return { PageStory };
}
export const parameters = {
viewMode: "story",
previewTabs: {
"storybook/docs/panel": {
hidden: true
}
}
};

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "code.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "code.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/code.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "delete-account-confirm.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "delete-account-confirm.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/delete-account-confirm.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "delete-credential.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "delete-credential.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/delete-credential.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "error.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "error.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/error.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "frontchannel-logout.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "frontchannel-logout.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/frontchannel-logout.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "idp-review-user-profile.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "idp-review-user-profile.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/idp-review-user-profile.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "info.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "info.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/info.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-config-totp.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-config-totp.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-config-totp.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-oauth2-device-verify-user-code.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-oauth2-device-verify-user-code.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-oauth2-device-verify-user-code.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-idp-link-confirm.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-idp-link-confirm.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-idp-link-confirm.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-idp-link-email.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-idp-link-email.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-idp-link-email.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-oauth2-device-verify-user-code.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-oauth2-device-verify-user-code.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-oauth2-device-verify-user-code.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-oauth-grant.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-oauth-grant.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-oauth-grant.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-otp.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-otp.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-otp.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-page-expired.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-page-expired.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-page-expired.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-password.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-password.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-recovery-authn-code-config.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-recovery-authn-code-config.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-recovery-authn-code-config.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-recovery-authn-code-input.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-recovery-authn-code-input.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-recovery-authn-code-input.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-reset-otp.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-reset-otp.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-reset-otp.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-reset-password.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-reset-password.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-reset-password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-update-password.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-update-password.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-update-password.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-update-profile.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-update-profile.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-update-profile.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-username.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-username.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-username.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-verify-email.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-verify-email.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-verify-email.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "login-x509-info.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "login-x509-info.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/login-x509-info.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "logout-confirm.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "logout-confirm.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/logout-confirm.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "register.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "register.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/register.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "saml-post-form.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "saml-post-form.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/saml-post-form.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "select-authenticator.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "select-authenticator.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/select-authenticator.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "terms.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "terms.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/terms.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "update-email.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "update-email.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/update-email.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "webauthn-authenticate.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "webauthn-authenticate.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/webauthn-authenticate.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "webauthn-error.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "webauthn-error.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/webauthn-error.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,15 +1,12 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { createPageStory, parameters } from "../PageStory";
import { createPageStory } from "../PageStory";
const pageId = "webauthn-register.ftl";
const { PageStory } = createPageStory({ pageId });
const { PageStory } = createPageStory({ pageId: "webauthn-register.ftl" });
const meta = {
title: `login/${pageId}`,
component: PageStory,
parameters
title: "login/webauthn-register.ftl",
component: PageStory
} satisfies Meta<typeof PageStory>;
export default meta;

View File

@ -1,4 +1,5 @@
import { type ExtendKcContext, createGetKcContextMock } from "keycloakify/login";
import type { ExtendKcContext } from "keycloakify/login";
import { createGetKcContextMock } from "keycloakify/login/KcContext";
import { KcContext as KcContextBase } from "keycloakify/login/KcContext";
import { assert, type Equals } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
@ -64,7 +65,7 @@ import { Reflect } from "tsafe/Reflect";
pageId: "login.ftl"
});
type Expected = KcContext;
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
@ -74,7 +75,7 @@ import { Reflect } from "tsafe/Reflect";
pageId: "register.ftl"
});
type Expected = KcContext;
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
@ -84,7 +85,7 @@ import { Reflect } from "tsafe/Reflect";
pageId: "my-custom-page.ftl"
});
type Expected = KcContext;
type Expected = Extract<KcContext, { pageId: "my-custom-page.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
@ -181,7 +182,7 @@ import { Reflect } from "tsafe/Reflect";
pageId: "login.ftl"
});
type Expected = KcContext;
type Expected = Extract<KcContext, { pageId: "login.ftl" }>;
assert<Equals<typeof got, Expected>>();
}
@ -191,7 +192,7 @@ import { Reflect } from "tsafe/Reflect";
pageId: "register.ftl"
});
type Expected = KcContext;
type Expected = Extract<KcContext, { pageId: "register.ftl" }>;
assert<Equals<typeof got, Expected>>();
}

View File

@ -1,5 +1,6 @@
import { id } from "tsafe/id";
import { createGetKcContextMock, type Attribute } from "keycloakify/login";
import type { Attribute } from "keycloakify/login";
import { createGetKcContextMock } from "keycloakify/login/KcContext";
import {
kcContextMocks,
kcContextCommonMock