Skip to content

Commit

Permalink
eject pages and add stories
Browse files Browse the repository at this point in the history
  • Loading branch information
FelixTJDietrich committed Nov 20, 2024
1 parent 9a018e2 commit 39cae0d
Show file tree
Hide file tree
Showing 16 changed files with 586 additions and 82 deletions.
Binary file modified keycloakify/dist_keycloak/keycloak-theme-for-kc-22-to-25.jar
Binary file not shown.
Binary file not shown.
5 changes: 4 additions & 1 deletion keycloakify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"build": "tsc && vite build",
"build-keycloak-theme": "npm run build && keycloakify build",
"storybook": "storybook dev -p 6006",
"format": "prettier . --write"
"format": "prettier . --write",
"start-keycloak": "npx keycloakify start-keycloak",
"eject-page": "npx keycloakify eject-page",
"add-story": "npx keycloakify add-story"
},
"license": "MIT",
"keywords": [],
Expand Down
98 changes: 50 additions & 48 deletions keycloakify/src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,63 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";

const alertVariants = cva(
"flex items-center gap-2 w-full rounded-lg border px-4 py-3 text-sm [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
error: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
success: "border-lime-500/50 text-lime-500 dark:border-lime-500 [&>svg]:text-lime-500",
warning: "border-yellow-500/50 text-yellow-500 dark:border-yellow-500 [&>svg]:text-yellow-500",
info: "border-blue-500/50 text-blue-500 dark:border-blue-500 [&>svg]:text-blue-500",
},
},
defaultVariants: {
variant: "default",
},
}
)
"flex items-center gap-2 w-full rounded-lg border px-4 py-3 text-sm [&>svg]:text-foreground",
{
variants: {
variant: {
default: "bg-background text-foreground",
error: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
success:
"border-lime-500/50 text-lime-500 dark:border-lime-500 [&>svg]:text-lime-500",
warning:
"border-yellow-500/50 text-yellow-500 dark:border-yellow-500 [&>svg]:text-yellow-500",
info: "border-blue-500/50 text-blue-500 dark:border-blue-500 [&>svg]:text-blue-500"
}
},
defaultVariants: {
variant: "default"
}
}
);

const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = "Alert";

const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
));
AlertTitle.displayName = "AlertTitle";

const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
));
AlertDescription.displayName = "AlertDescription";

export { Alert, AlertTitle, AlertDescription }
export { Alert, AlertTitle, AlertDescription };
38 changes: 38 additions & 0 deletions keycloakify/src/login/KcPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const UserProfileFormFields = lazy(
() => import("keycloakify/login/UserProfileFormFields")
);
const Login = lazy(() => import("./pages/Login"));
const Info = lazy(() => import("./pages/Info"));
const Error = lazy(() => import("./pages/Error"));
const LoginUpdateProfile = lazy(() => import("./pages/LoginUpdateProfile"));
const LoginPageExpired = lazy(() => import("./pages/LoginPageExpired"));

const doMakeUserConfirmPassword = true;

Expand Down Expand Up @@ -40,6 +44,40 @@ function KcPageContextualized(props: { kcContext: KcContext }) {
doUseDefaultCss={false}
/>
);
case "info.ftl":
return (
<Info
{...{ kcContext, i18n, classes }}
Template={Template}
doUseDefaultCss={false}
/>
);
case "error.ftl":
return (
<Error
{...{ kcContext, i18n, classes }}
Template={Template}
doUseDefaultCss={false}
/>
);
case "login-update-profile.ftl":
return (
<LoginUpdateProfile
{...{ kcContext, i18n, classes }}
Template={Template}
doUseDefaultCss={false}
UserProfileFormFields={UserProfileFormFields}
doMakeUserConfirmPassword={doMakeUserConfirmPassword}
/>
);
case "login-page-expired.ftl":
return (
<LoginPageExpired
{...{ kcContext, i18n, classes }}
Template={Template}
doUseDefaultCss={false}
/>
);
default:
return (
<DefaultPage
Expand Down
4 changes: 3 additions & 1 deletion keycloakify/src/login/Template.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
<header>
{(() => {
const node = !(auth !== undefined && auth.showUsername && !auth.showResetCredentials) ? (
<h1 id="kc-page-title" className="text-xl text-center">{headerNode}</h1>
<h1 id="kc-page-title" className="text-xl text-center">
{headerNode}
</h1>
) : (
<div id="kc-username" className={kcClsx("kcFormGroupClass")}>
<label id="kc-attempted-username">{auth.attemptedUsername}</label>
Expand Down
62 changes: 62 additions & 0 deletions keycloakify/src/login/pages/Error.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "error.ftl" });

const meta = {
title: "login/error.ftl",
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => <KcPageStory />
};

export const WithAnotherMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "With another error message" }
}}
/>
)
};

export const WithHtmlErrorMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: {
summary: "<strong>Error:</strong> Something went wrong. <a href='https://example.com'>Go back</a>"
}
}}
/>
)
};
export const FrenchError: Story = {
render: () => (
<KcPageStory
kcContext={{
locale: { currentLanguageTag: "fr" },
message: { summary: "Une erreur s'est produite" }
}}
/>
)
};
export const WithSkipLink: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "An error occurred" },
skipLink: true,
client: {
baseUrl: "https://example.com"
}
}}
/>
)
};
34 changes: 34 additions & 0 deletions keycloakify/src/login/pages/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { PageProps } from "keycloakify/login/pages/PageProps";
import { kcSanitize } from "keycloakify/lib/kcSanitize";
import type { KcContext } from "../KcContext";
import type { I18n } from "../i18n";

export default function Error(props: PageProps<Extract<KcContext, { pageId: "error.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;

const { message, client, skipLink } = kcContext;

const { msg } = i18n;

return (
<Template
kcContext={kcContext}
i18n={i18n}
doUseDefaultCss={doUseDefaultCss}
classes={classes}
displayMessage={false}
headerNode={msg("errorTitle")}
>
<div id="kc-error-message">
<p className="instruction" dangerouslySetInnerHTML={{ __html: kcSanitize(message.summary) }} />
{!skipLink && client !== undefined && client.baseUrl !== undefined && (
<p>
<a id="backToApplication" href={client.baseUrl}>
{msg("backToApplication")}
</a>
</p>
)}
</div>
</Template>
);
}
95 changes: 95 additions & 0 deletions keycloakify/src/login/pages/Info.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { Meta, StoryObj } from "@storybook/react";
import { createKcPageStory } from "../KcPageStory";

const { KcPageStory } = createKcPageStory({ pageId: "info.ftl" });

const meta = {
title: "login/info.ftl",
component: KcPageStory
} satisfies Meta<typeof KcPageStory>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: () => (
<KcPageStory
kcContext={{
message: {
summary: "Server info message"
}
}}
/>
)
};

export const WithLinkBack: Story = {
render: () => (
<KcPageStory
kcContext={{
message: {
summary: "Server message"
},
actionUri: undefined
}}
/>
)
};

export const WithRequiredActions: Story = {
render: () => (
<KcPageStory
kcContext={{
message: {
summary: "Required actions: "
},
requiredActions: ["CONFIGURE_TOTP", "UPDATE_PROFILE", "VERIFY_EMAIL", "CUSTOM_ACTION"],
"x-keycloakify": {
messages: {
"requiredAction.CUSTOM_ACTION": "Custom action"
}
}
}}
/>
)
};
export const WithPageRedirect: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "You will be redirected shortly." },
pageRedirectUri: "https://example.com"
}}
/>
)
};
export const WithoutClientBaseUrl: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "No client base URL defined." },
client: { baseUrl: undefined }
}}
/>
)
};
export const WithMessageHeader: Story = {
render: () => (
<KcPageStory
kcContext={{
messageHeader: "Important Notice",
message: { summary: "This is an important message." }
}}
/>
)
};
export const WithAdvancedMessage: Story = {
render: () => (
<KcPageStory
kcContext={{
message: { summary: "Please take note of this <strong>important</strong> information." }
}}
/>
)
};
Loading

0 comments on commit 39cae0d

Please sign in to comment.