forked from keycloakify/keycloakify-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Eject login page * Demo Tailwind * Update keycloakify and remove account theme boilerplate * Update keycloakify * update keycloakify * Update to Keycloakify v10 * Update Keycloakify * Update README.md * Update keycloakify * update keycloakify * Update keycloakify * Update keycloakify * update readme * add engines * Update keycloakify * Update to Keycloakify 11 * update prettierignore * Update keycloakify * Update keycloakify * Update keycloakify * update keycloakify * Update keycloakify * update keycloakify * Update keycloakify and fmt * Update keycloakify * Update keycloakify * keycloakify#40 * Update keycloakify * Update documentation links * Update keycloakify * Update keycloakify * Update keycloakify * Bump keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Update keycloakify * Updat keycloakify * Update package.json --------- Co-authored-by: Joseph Garrone <joseph.garrone.gj@gmail.com>
1 parent
9002e0a
commit 4357120
Showing
22 changed files
with
638 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,3 +46,4 @@ export default typescriptEslint.config( | |
}, | ||
}, | ||
); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default { | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: {} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Black.woff2") format("woff2"); | ||
font-weight: 900; | ||
/* Black */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Bold.woff2") format("woff2"); | ||
font-weight: bold; | ||
/* Bold */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Light.woff2") format("woff2"); | ||
font-weight: 300; | ||
/* Light */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Medium.woff2") format("woff2"); | ||
font-weight: 500; | ||
/* Medium */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Regular.woff2") format("woff2"); | ||
font-weight: 400; | ||
/* Regular */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-SemiBold.woff2") format("woff2"); | ||
font-weight: 600; | ||
/* SemiBold */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-Thin.woff2") format("woff2"); | ||
font-weight: 100; | ||
/* Thin */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-UltraLight.woff2") format("woff2"); | ||
font-weight: 200; | ||
/* UltraLight */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist"; | ||
src: url("./Geist-UltraBlack.woff2") format("woff2"); | ||
font-weight: 950; | ||
/* UltraBlack */ | ||
font-style: normal; | ||
} | ||
|
||
@font-face { | ||
font-family: "Geist Variable"; | ||
src: url("./GeistVariableVF.woff2") format("woff2"); | ||
font-weight: 100 950; | ||
/* Range from Thin to UltraBlack */ | ||
font-style: normal; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
@import url(./assets/fonts/geist/index.css); | ||
|
||
@tailwind base; | ||
@tailwind components; | ||
@tailwind utilities; | ||
|
||
body.kcBodyClass { | ||
@apply bg-[url(./assets/img/background.jpg)] bg-no-repeat bg-center bg-fixed; | ||
@apply font-geist; | ||
} | ||
|
||
.kcHeaderWrapperClass { | ||
@apply text-3xl font-bold underline; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
import { useState, useEffect, useReducer } from "react"; | ||
import { kcSanitize } from "keycloakify/lib/kcSanitize"; | ||
import { assert } from "keycloakify/tools/assert"; | ||
import { clsx } from "keycloakify/tools/clsx"; | ||
import type { PageProps } from "keycloakify/login/pages/PageProps"; | ||
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx"; | ||
import type { KcContext } from "../KcContext"; | ||
import type { I18n } from "../i18n"; | ||
|
||
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) { | ||
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; | ||
|
||
const { kcClsx } = getKcClsx({ | ||
doUseDefaultCss, | ||
classes | ||
}); | ||
|
||
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } = kcContext; | ||
|
||
const { msg, msgStr } = i18n; | ||
|
||
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false); | ||
|
||
return ( | ||
<Template | ||
kcContext={kcContext} | ||
i18n={i18n} | ||
doUseDefaultCss={doUseDefaultCss} | ||
classes={classes} | ||
displayMessage={!messagesPerField.existsError("username", "password")} | ||
headerNode={msg("loginAccountTitle")} | ||
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled} | ||
infoNode={ | ||
<div id="kc-registration-container"> | ||
<div id="kc-registration"> | ||
<span> | ||
{msg("noAccount")}{" "} | ||
<a tabIndex={8} href={url.registrationUrl}> | ||
{msg("doRegister")} | ||
</a> | ||
</span> | ||
</div> | ||
</div> | ||
} | ||
socialProvidersNode={ | ||
<> | ||
{realm.password && social?.providers !== undefined && social.providers.length !== 0 && ( | ||
<div id="kc-social-providers" className={kcClsx("kcFormSocialAccountSectionClass")}> | ||
<hr /> | ||
<h2>{msg("identity-provider-login-label")}</h2> | ||
<ul className={kcClsx("kcFormSocialAccountListClass", social.providers.length > 3 && "kcFormSocialAccountListGridClass")}> | ||
{social.providers.map((...[p, , providers]) => ( | ||
<li key={p.alias}> | ||
<a | ||
id={`social-${p.alias}`} | ||
className={kcClsx( | ||
"kcFormSocialAccountListButtonClass", | ||
providers.length > 3 && "kcFormSocialAccountGridItem" | ||
)} | ||
type="button" | ||
href={p.loginUrl} | ||
> | ||
{p.iconClasses && <i className={clsx(kcClsx("kcCommonLogoIdP"), p.iconClasses)} aria-hidden="true"></i>} | ||
<span | ||
className={clsx(kcClsx("kcFormSocialAccountNameClass"), p.iconClasses && "kc-social-icon-text")} | ||
dangerouslySetInnerHTML={{ __html: kcSanitize(p.displayName) }} | ||
></span> | ||
</a> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
</> | ||
} | ||
> | ||
<div id="kc-form"> | ||
<div id="kc-form-wrapper"> | ||
{realm.password && ( | ||
<form | ||
id="kc-form-login" | ||
onSubmit={() => { | ||
setIsLoginButtonDisabled(true); | ||
return true; | ||
}} | ||
action={url.loginAction} | ||
method="post" | ||
> | ||
{!usernameHidden && ( | ||
<div className={kcClsx("kcFormGroupClass")}> | ||
<label htmlFor="username" className={kcClsx("kcLabelClass")}> | ||
{!realm.loginWithEmailAllowed | ||
? msg("username") | ||
: !realm.registrationEmailAsUsername | ||
? msg("usernameOrEmail") | ||
: msg("email")} | ||
</label> | ||
<input | ||
tabIndex={2} | ||
id="username" | ||
className={kcClsx("kcInputClass")} | ||
name="username" | ||
defaultValue={login.username ?? ""} | ||
type="text" | ||
autoFocus | ||
autoComplete="username" | ||
aria-invalid={messagesPerField.existsError("username", "password")} | ||
/> | ||
{messagesPerField.existsError("username", "password") && ( | ||
<span | ||
id="input-error" | ||
className={kcClsx("kcInputErrorMessageClass")} | ||
aria-live="polite" | ||
dangerouslySetInnerHTML={{ | ||
__html: kcSanitize(messagesPerField.getFirstError("username", "password")) | ||
}} | ||
/> | ||
)} | ||
</div> | ||
)} | ||
|
||
<div className={kcClsx("kcFormGroupClass")}> | ||
<label htmlFor="password" className={kcClsx("kcLabelClass")}> | ||
{msg("password")} | ||
</label> | ||
<PasswordWrapper kcClsx={kcClsx} i18n={i18n} passwordInputId="password"> | ||
<input | ||
tabIndex={3} | ||
id="password" | ||
className={kcClsx("kcInputClass")} | ||
name="password" | ||
type="password" | ||
autoComplete="current-password" | ||
aria-invalid={messagesPerField.existsError("username", "password")} | ||
/> | ||
</PasswordWrapper> | ||
{usernameHidden && messagesPerField.existsError("username", "password") && ( | ||
<span | ||
id="input-error" | ||
className={kcClsx("kcInputErrorMessageClass")} | ||
aria-live="polite" | ||
dangerouslySetInnerHTML={{ | ||
__html: kcSanitize(messagesPerField.getFirstError("username", "password")) | ||
}} | ||
/> | ||
)} | ||
</div> | ||
|
||
<div className={kcClsx("kcFormGroupClass", "kcFormSettingClass")}> | ||
<div id="kc-form-options"> | ||
{realm.rememberMe && !usernameHidden && ( | ||
<div className="checkbox"> | ||
<label> | ||
<input | ||
tabIndex={5} | ||
id="rememberMe" | ||
name="rememberMe" | ||
type="checkbox" | ||
defaultChecked={!!login.rememberMe} | ||
/>{" "} | ||
{msg("rememberMe")} | ||
</label> | ||
</div> | ||
)} | ||
</div> | ||
<div className={kcClsx("kcFormOptionsWrapperClass")}> | ||
{realm.resetPasswordAllowed && ( | ||
<span> | ||
<a tabIndex={6} href={url.loginResetCredentialsUrl}> | ||
{msg("doForgotPassword")} | ||
</a> | ||
</span> | ||
)} | ||
</div> | ||
</div> | ||
|
||
<div id="kc-form-buttons" className={kcClsx("kcFormGroupClass")}> | ||
<input type="hidden" id="id-hidden-input" name="credentialId" value={auth.selectedCredential} /> | ||
<input | ||
tabIndex={7} | ||
disabled={isLoginButtonDisabled} | ||
className={clsx( | ||
kcClsx("kcButtonClass", "kcButtonPrimaryClass", "kcButtonBlockClass", "kcButtonLargeClass"), | ||
"rounded-lg" | ||
)} | ||
name="login" | ||
id="kc-login" | ||
type="submit" | ||
value={msgStr("doLogIn")} | ||
/> | ||
</div> | ||
</form> | ||
)} | ||
</div> | ||
</div> | ||
</Template> | ||
); | ||
} | ||
|
||
function PasswordWrapper(props: { kcClsx: KcClsx; i18n: I18n; passwordInputId: string; children: JSX.Element }) { | ||
const { kcClsx, i18n, passwordInputId, children } = props; | ||
|
||
const { msgStr } = i18n; | ||
|
||
const [isPasswordRevealed, toggleIsPasswordRevealed] = useReducer((isPasswordRevealed: boolean) => !isPasswordRevealed, false); | ||
|
||
useEffect(() => { | ||
const passwordInputElement = document.getElementById(passwordInputId); | ||
|
||
assert(passwordInputElement instanceof HTMLInputElement); | ||
|
||
passwordInputElement.type = isPasswordRevealed ? "text" : "password"; | ||
}, [isPasswordRevealed]); | ||
|
||
return ( | ||
<div className={kcClsx("kcInputGroup")}> | ||
{children} | ||
<button | ||
type="button" | ||
className={kcClsx("kcFormPasswordVisibilityButtonClass")} | ||
aria-label={msgStr(isPasswordRevealed ? "hidePassword" : "showPassword")} | ||
aria-controls={passwordInputId} | ||
onClick={toggleIsPasswordRevealed} | ||
> | ||
<i className={kcClsx(isPasswordRevealed ? "kcFormPasswordVisibilityIconHide" : "kcFormPasswordVisibilityIconShow")} aria-hidden /> | ||
</button> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** @type {import('tailwindcss').Config} */ | ||
export default { | ||
content: ["./index.html", "./src/**/*.{ts,tsx}"], | ||
theme: { | ||
extend: { | ||
fontFamily: { | ||
geist: ["Geist", "sans-serif"] | ||
} | ||
} | ||
}, | ||
plugins: [] | ||
}; |