fix: linting for login-quality

This commit is contained in:
Max Peintner
2025-07-14 11:31:53 +02:00
parent 84a9946231
commit ab79297b5c
71 changed files with 263 additions and 246 deletions

View File

@@ -52,7 +52,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Run login quality checks with Turbo
run: pnpm turbo lint test:unit --filter=./login
run: pnpm turbo test:unit --filter=@zitadel/login
env:
# latest if branch is main, otherwise image version which is the pull request number
LOGIN_BAKE_CLI: depot bake

View File

@@ -7,8 +7,8 @@ module.exports = {
"@next/next/no-img-element": "off",
"react/no-unescaped-entities": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-undef": "off"
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"no-undef": "off",
},
parserOptions: {
ecmaVersion: "latest",

View File

@@ -5,7 +5,7 @@
"scripts": {
"dev": "next dev",
"dev:turbo": "next dev --turbopack",
"test:unit": "pnpm vitest",
"test:unit": "pnpm vitest --run",
"test:unit:standalone": "pnpm test:unit",
"test:unit:watch": "pnpm test:unit --watch",
"lint": "pnpm exec next lint && pnpm exec prettier --check .",

View File

@@ -4,8 +4,8 @@ export default {
useTabs: false,
semi: true,
singleQuote: false,
trailingComma: 'all',
trailingComma: "all",
bracketSpacing: true,
arrowParens: 'always',
plugins: ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"]
arrowParens: "always",
plugins: ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"],
};

View File

@@ -5,41 +5,49 @@
* This script converts the monorepo version to a standalone version
*/
import fs from 'fs/promises';
import { execSync } from 'child_process';
import { execSync } from "child_process";
import fs from "fs/promises";
const FILES_TO_REMOVE = [
// Turbo is not needed for standalone builds since there are no workspace dependencies
{ file: 'turbo.json', backup: 'turbo.monorepo.backup.json' }
{ file: "turbo.json", backup: "turbo.monorepo.backup.json" },
];
async function prepareStandalone() {
console.log('🔧 Preparing standalone version...\n');
console.log("🔧 Preparing standalone version...\n");
const args = process.argv.slice(2);
const shouldInstall = args.includes('--install');
const shouldInstall = args.includes("--install");
try {
// Step 1: Copy package.standalone.json to package.json
console.log('📦 Setting up package.json...');
const packageStandaloneExists = await fs.access('package.standalone.json').then(() => true).catch(() => false);
console.log("📦 Setting up package.json...");
const packageStandaloneExists = await fs
.access("package.standalone.json")
.then(() => true)
.catch(() => false);
if (packageStandaloneExists) {
// Backup current package.json
await fs.copyFile('package.json', 'package.monorepo.backup.json');
console.log(' 💾 Backed up package.json → package.monorepo.backup.json');
await fs.copyFile("package.json", "package.monorepo.backup.json");
console.log(
" 💾 Backed up package.json → package.monorepo.backup.json",
);
// Copy standalone version
await fs.copyFile('package.standalone.json', 'package.json');
console.log(' ✅ package.standalone.json → package.json');
await fs.copyFile("package.standalone.json", "package.json");
console.log(" ✅ package.standalone.json → package.json");
} else {
throw new Error('package.standalone.json not found!');
throw new Error("package.standalone.json not found!");
}
// Step 2: Remove unnecessary files for standalone
console.log('🗑️ Removing monorepo-specific files...');
console.log("🗑️ Removing monorepo-specific files...");
for (const item of FILES_TO_REMOVE) {
const fileExists = await fs.access(item.file).then(() => true).catch(() => false);
const fileExists = await fs
.access(item.file)
.then(() => true)
.catch(() => false);
if (fileExists) {
// Backup current file
@@ -56,27 +64,28 @@ async function prepareStandalone() {
// Step 3: Install dependencies if requested
if (shouldInstall) {
console.log('\n📥 Installing dependencies...');
console.log("\n📥 Installing dependencies...");
try {
execSync('pnpm install', { stdio: 'inherit' });
console.log(' ✅ Dependencies installed successfully');
execSync("pnpm install", { stdio: "inherit" });
console.log(" ✅ Dependencies installed successfully");
} catch (error) {
console.warn(' ⚠️ pnpm install failed, you may need to run it manually');
console.warn(
" ⚠️ pnpm install failed, you may need to run it manually",
);
}
}
console.log('\n🎉 Standalone preparation complete!');
console.log(' ✨ Turbo removed - using standard npm scripts');
console.log('\n📋 Next steps:');
console.log("\n🎉 Standalone preparation complete!");
console.log(" ✨ Turbo removed - using standard npm scripts");
console.log("\n📋 Next steps:");
if (!shouldInstall) {
console.log(' 1. Run: pnpm install');
console.log(" 1. Run: pnpm install");
}
console.log(' 2. Run: pnpm run dev');
console.log(' 3. Start developing!\n');
console.log(" 2. Run: pnpm run dev");
console.log(" 3. Start developing!\n");
} catch (error) {
console.error('\n❌ Failed to prepare standalone version:', error.message);
console.error('Please check the error above and try again.\n');
console.error("\n❌ Failed to prepare standalone version:", error.message);
console.error("Please check the error above and try again.\n");
process.exit(1);
}
}

View File

@@ -10,7 +10,7 @@ import {
} from "@/lib/zitadel";
import { UserPlusIcon } from "@heroicons/react/24/outline";
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { getLocale } from "next-intl/server";
// import { getLocale } from "next-intl/server";
import { headers } from "next/headers";
import Link from "next/link";
@@ -33,7 +33,7 @@ export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {
const searchParams = await props.searchParams;
const locale = getLocale();
// const locale = getLocale();
const requestId = searchParams?.requestId;
const organization = searchParams?.organization;
@@ -78,11 +78,11 @@ export default async function Page(props: {
<Translated i18nKey="description" namespace="accounts" />
</p>
<div className="flex flex-col w-full space-y-2">
<div className="flex w-full flex-col space-y-2">
<SessionsList sessions={sessions} requestId={requestId} />
<Link href={`/loginname?` + params}>
<div className="flex flex-row items-center py-3 px-4 hover:bg-black/10 dark:hover:bg-white/10 rounded-md transition-all">
<div className="w-8 h-8 mr-4 flex flex-row justify-center items-center rounded-full bg-black/5 dark:bg-white/5">
<div className="flex flex-row items-center rounded-md px-4 py-3 transition-all hover:bg-black/10 dark:hover:bg-white/10">
<div className="mr-4 flex h-8 w-8 flex-row items-center justify-center rounded-full bg-black/5 dark:bg-white/5">
<UserPlusIcon className="h-5 w-5" />
</div>
<span className="text-sm">

View File

@@ -18,7 +18,7 @@ import {
listAuthenticationMethodTypes,
} from "@/lib/zitadel";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import { getLocale } from "next-intl/server";
// import { getLocale } from "next-intl/server";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
@@ -26,7 +26,7 @@ export default async function Page(props: {
searchParams: Promise<Record<string | number | symbol, string | undefined>>;
}) {
const searchParams = await props.searchParams;
const locale = getLocale();
// const locale = getLocale();
const { loginName, requestId, organization, sessionId } = searchParams;
@@ -193,7 +193,7 @@ export default async function Page(props: {
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
<>
<div className="py-3 flex flex-col">
<div className="flex flex-col py-3">
<p className="ztdl-p text-center">
<Translated i18nKey="linkWithIDP" namespace="authenticator" />
</p>

View File

@@ -27,13 +27,13 @@ export default async function RootLayout({
<Suspense
fallback={
<div
className={`relative min-h-screen bg-background-light-600 dark:bg-background-dark-600 flex flex-col justify-center`}
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
>
<div className="relative mx-auto max-w-[440px] py-8 w-full">
<div className="relative mx-auto w-full max-w-[440px] py-8">
<Skeleton>
<div className="h-40"></div>
</Skeleton>
<div className="flex flex-row justify-end py-4 items-center space-x-4">
<div className="flex flex-row items-center justify-end space-x-4 py-4">
<Theme />
</div>
</div>
@@ -42,11 +42,11 @@ export default async function RootLayout({
>
<LanguageProvider>
<div
className={`relative min-h-screen bg-background-light-600 dark:bg-background-dark-600 flex flex-col justify-center`}
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
>
<div className="relative mx-auto max-w-[440px] py-8 w-full ">
<div className="relative mx-auto w-full max-w-[440px] py-8">
{children}
<div className="flex flex-row justify-end py-4 items-center space-x-4">
<div className="flex flex-row items-center justify-end space-x-4 py-4">
<LanguageSwitcher />
<Theme />
</div>

View File

@@ -79,7 +79,7 @@ export default async function Page(props: {
></UsernameForm>
{identityProviders && loginSettings?.allowExternalIdp && (
<div className="w-full pt-6 pb-4">
<div className="w-full pb-4 pt-6">
<SignInWithIdp
identityProviders={identityProviders}
requestId={requestId}

View File

@@ -72,7 +72,7 @@ export default async function Page(props: {
<Translated i18nKey="description" namespace="logout" />
</p>
<div className="flex flex-col w-full space-y-2">
<div className="flex w-full flex-col space-y-2">
<SessionsClearList
sessions={sessions}
logoutHint={logoutHint}

View File

@@ -54,7 +54,7 @@ export default async function Page(props: {
<span>
<Translated i18nKey="set.info.description" namespace="passkey" />
<a
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
className="text-primary-light-500 hover:text-primary-light-300 dark:text-primary-dark-500 hover:dark:text-primary-dark-300"
target="_blank"
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
>

View File

@@ -117,7 +117,7 @@ export default async function Page(props: {
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
<>
<div className="py-3 flex flex-col items-center">
<div className="flex flex-col items-center py-3">
<p className="ztdl-p text-center">
<Translated i18nKey="orUseIDP" namespace="register" />
</p>

View File

@@ -136,7 +136,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
)}
{id && send && (
<div className="py-4 w-full">
<div className="w-full py-4">
<Alert type={AlertType.INFO}>
<Translated i18nKey="verify.codeSent" namespace="verify" />
</Alert>

View File

@@ -11,7 +11,7 @@ export function AddressBar({ domain }: Props) {
const pathname = usePathname();
return (
<div className="flex items-center space-x-2 p-3.5 lg:px-5 lg:py-3 overflow-hidden">
<div className="flex items-center space-x-2 overflow-hidden p-3.5 lg:px-5 lg:py-3">
<div className="text-gray-600">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -27,7 +27,7 @@ export function AddressBar({ domain }: Props) {
</svg>
</div>
<div className="flex space-x-1 text-sm font-medium">
<div className="max-w-[150px] px-2 overflow-hidden text-gray-500 text-ellipsis">
<div className="max-w-[150px] overflow-hidden text-ellipsis px-2 text-gray-500">
<span className="whitespace-nowrap">{domain}</span>
</div>
{pathname ? (

View File

@@ -26,7 +26,7 @@ export function Alert({ children, type = AlertType.ALERT }: Props) {
return (
<div
className={clsx(
"flex flex-row items-center justify-center border rounded-md py-2 pr-2 scroll-px-40",
"flex scroll-px-40 flex-row items-center justify-center rounded-md border py-2 pr-2",
{
[yellow]: type === AlertType.ALERT,
[neutral]: type === AlertType.INFO,
@@ -34,12 +34,12 @@ export function Alert({ children, type = AlertType.ALERT }: Props) {
)}
>
{type === AlertType.ALERT && (
<ExclamationTriangleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
<ExclamationTriangleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
)}
{type === AlertType.INFO && (
<InformationCircleIcon className="flex-shrink-0 h-5 w-5 mr-2 ml-2" />
<InformationCircleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
)}
<span className="text-sm w-full ">{children}</span>
<span className="w-full text-sm">{children}</span>
</div>
);
}

View File

@@ -27,7 +27,7 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
return (
<div
className={`w-[100px] h-[100px] flex justify-center items-center cursor-default pointer-events-none group-focus:outline-none group-focus:ring-2 transition-colors duration-200 dark:group-focus:ring-offset-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-500 group-focus:ring-primary-light-200 dark:group-focus:ring-primary-dark-400 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 dark:text-blue rounded-full ${
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-[100px] w-[100px] cursor-default items-center justify-center rounded-full bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${
shadow ? "shadow" : ""
}`}
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
@@ -37,11 +37,11 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
height={48}
width={48}
alt="avatar"
className="w-full h-full border border-divider-light dark:border-divider-dark rounded-full"
className="h-full w-full rounded-full border border-divider-light dark:border-divider-dark"
src={imageUrl}
/>
) : (
<span className={`uppercase text-3xl`}>{credentials}</span>
<span className={`text-3xl uppercase`}>{credentials}</span>
)}
</div>
);

View File

@@ -35,12 +35,12 @@ export const TOTP = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "opacity-50" : "",
)}
>
<svg
className="h-8 w-8 transform -translate-x-[2px] mr-4 fill-current text-black dark:text-white"
className="mr-4 h-8 w-8 -translate-x-[2px] transform fill-current text-black dark:text-white"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
@@ -63,7 +63,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "" : "",
)}
>
@@ -73,7 +73,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-8 h-8 mr-4"
className="mr-4 h-8 w-8"
>
<path
strokeLinecap="round"
@@ -97,12 +97,12 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "" : "",
)}
>
<svg
className="w-8 h-8 mr-4"
className="mr-4 h-8 w-8"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
@@ -132,12 +132,12 @@ export const SMS = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "" : "",
)}
>
<svg
className="w-8 h-8 mr-4"
className="mr-4 h-8 w-8"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
@@ -166,7 +166,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "" : "",
)}
>
@@ -176,7 +176,7 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-8 h-8 mr-4"
className="mr-4 h-8 w-8"
>
<path
strokeLinecap="round"
@@ -200,12 +200,12 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => {
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
<div
className={clsx(
"font-medium flex items-center",
"flex items-center font-medium",
alreadyAdded ? "" : "",
)}
>
<svg
className="w-8 h-7 mr-4 fill-current"
className="mr-4 h-7 w-8 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
@@ -225,9 +225,9 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => {
function Setup() {
return (
<div className="transform absolute right-2 top-0">
<div className="absolute right-2 top-0 transform">
<StateBadge evenPadding={true} state={BadgeState.Success}>
<CheckIcon className="w-4 h-4" />
<CheckIcon className="h-4 w-4" />
</StateBadge>
</div>
);

View File

@@ -34,20 +34,18 @@ export function AuthenticationMethodRadio({
className={({ active, checked }) =>
`${
active
? "ring-2 ring-opacity-60 ring-primary-light-500 dark:ring-white/20"
? "ring-2 ring-primary-light-500 ring-opacity-60 dark:ring-white/20"
: ""
}
${
} ${
checked
? "bg-background-light-400 dark:bg-background-dark-400 ring-2 ring-primary-light-500 dark:ring-primary-dark-500"
? "bg-background-light-400 ring-2 ring-primary-light-500 dark:bg-background-dark-400 dark:ring-primary-dark-500"
: "bg-background-light-400 dark:bg-background-dark-400"
}
h-full flex-1 relative border boder-divider-light dark:border-divider-dark flex cursor-pointer rounded-lg px-5 py-4 focus:outline-none hover:shadow-lg dark:hover:bg-white/10`
} boder-divider-light relative flex h-full flex-1 cursor-pointer rounded-lg border px-5 py-4 hover:shadow-lg focus:outline-none dark:border-divider-dark dark:hover:bg-white/10`
}
>
{({ active, checked }) => (
<>
<div className="flex flex-col items-center w-full text-sm">
<div className="flex w-full flex-col items-center text-sm">
{method === "passkey" && (
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -55,7 +53,7 @@ export function AuthenticationMethodRadio({
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-8 h-8 mb-3"
className="mb-3 h-8 w-8"
>
<path
strokeLinecap="round"
@@ -66,7 +64,7 @@ export function AuthenticationMethodRadio({
)}
{method === "password" && (
<svg
className="w-8 h-8 mb-3 fill-current"
className="mb-3 h-8 w-8 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>

View File

@@ -64,16 +64,16 @@ export function Avatar({
return (
<div
className={`w-full h-full flex-shrink-0 flex justify-center items-center cursor-default pointer-events-none group-focus:outline-none group-focus:ring-2 transition-colors duration-200 dark:group-focus:ring-offset-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-500 group-focus:ring-primary-light-200 dark:group-focus:ring-primary-dark-400 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 dark:text-blue rounded-full ${
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-full w-full flex-shrink-0 cursor-default items-center justify-center rounded-full bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${
shadow ? "shadow" : ""
} ${
size === "large"
? "h-20 w-20 font-normal"
: size === "base"
? "w-[38px] h-[38px] font-bold"
? "h-[38px] w-[38px] font-bold"
: size === "small"
? "!w-[32px] !h-[32px] font-bold text-[13px]"
: "w-12 h-12"
? "!h-[32px] !w-[32px] text-[13px] font-bold"
: "h-12 w-12"
}`}
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
>
@@ -82,7 +82,7 @@ export function Avatar({
height={48}
width={48}
alt="avatar"
className="w-full h-full border border-divider-light dark:border-divider-dark rounded-full"
className="h-full w-full rounded-full border border-divider-light dark:border-divider-dark"
src={imageUrl}
/>
) : (

View File

@@ -33,8 +33,7 @@ export const getButtonClasses = (
color: ButtonColors,
) =>
clsx({
"box-border font-normal leading-36px text-14px inline-flex items-center rounded-md focus:outline-none transition-colors transition-shadow duration-300":
true,
"box-border font-normal leading-36px text-14px inline-flex items-center rounded-md focus:outline-none transition-colors transition-shadow duration-300": true,
"shadow hover:shadow-xl active:shadow-xl disabled:border-none disabled:bg-gray-300 disabled:text-gray-600 disabled:shadow-none disabled:cursor-not-allowed disabled:dark:bg-gray-800 disabled:dark:text-gray-900":
variant === ButtonVariants.Primary,
"bg-primary-light-500 dark:bg-primary-dark-500 hover:bg-primary-light-400 hover:dark:bg-primary-dark-400 text-primary-light-contrast-500 dark:text-primary-dark-contrast-500":

View File

@@ -149,7 +149,7 @@ export function ChangePasswordForm({
return (
<form className="w-full">
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
<div className="">
<TextInput
type="password"
@@ -202,7 +202,7 @@ export function ChangePasswordForm({
onClick={handleSubmit(submitChange)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="change.submit" namespace="password" />
</Button>
</div>

View File

@@ -36,7 +36,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
return (
<div className="relative flex items-start">
<div className="flex items-center h-5">
<div className="flex h-5 items-center">
<div className="box-sizing block">
<input
ref={ref}
@@ -48,7 +48,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
disabled={disabled}
type="checkbox"
className={classNames(
"form-checkbox rounded border-gray-300 text-primary-light-500 dark:text-primary-dark-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50",
"form-checkbox rounded border-gray-300 text-primary-light-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 focus:ring-offset-0 dark:text-primary-dark-500",
className,
)}
{...props}

View File

@@ -25,7 +25,7 @@ export function ChooseAuthenticatorToLogin({
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
</div>
)}
<div className="grid grid-cols-1 gap-5 w-full pt-4">
<div className="grid w-full grid-cols-1 gap-5 pt-4">
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
loginSettings?.allowUsernamePassword &&
PASSWORD(false, "/password?" + params)}

View File

@@ -37,7 +37,7 @@ export function ChooseAuthenticatorToSetup({
</Alert>
)}
<div className="grid grid-cols-1 gap-5 w-full pt-4">
<div className="grid w-full grid-cols-1 gap-5 pt-4">
{!authMethods.includes(AuthenticationMethodType.PASSWORD) &&
loginSettings.allowUsernamePassword &&
PASSWORD(false, "/password/set?" + params)}

View File

@@ -58,7 +58,7 @@ export function ChooseSecondFactorToSetup({
return (
<>
<div className="grid grid-cols-1 gap-5 w-full pt-4">
<div className="grid w-full grid-cols-1 gap-5 pt-4">
{loginSettings.secondFactors.map((factor) => {
switch (factor) {
case SecondFactorType.OTP:
@@ -94,7 +94,7 @@ export function ChooseSecondFactorToSetup({
</div>
{!force && (
<button
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
onClick={async () => {
const resp = await skipMFAAndContinueWithNextUrl({
userId,

View File

@@ -34,7 +34,7 @@ export function ChooseSecondFactor({
}
return (
<div className="grid grid-cols-1 gap-5 w-full pt-4">
<div className="grid w-full grid-cols-1 gap-5 pt-4">
{userMethods.map((method, i) => {
return (
<div key={"method-" + i}>

View File

@@ -47,10 +47,10 @@ export function ConsentScreen({
const scopes = scope?.filter((s) => !!s);
return (
<div className="pt-4 w-full flex flex-col items-center space-y-4">
<ul className="list-disc space-y-2 w-full">
<div className="flex w-full flex-col items-center space-y-4 pt-4">
<ul className="w-full list-disc space-y-2">
{scopes?.length === 0 && (
<span className="w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all">
<span className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400">
<Translated i18nKey="device.scope.openid" namespace="device" />
</span>
)}
@@ -65,7 +65,7 @@ export function ConsentScreen({
return (
<li
key={s}
className="w-full text-sm flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light py-2 px-4 rounded-md transition-all"
className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400"
>
<span>{resolvedDescription}</span>
</li>
@@ -73,7 +73,7 @@ export function ConsentScreen({
})}
</ul>
<p className="ztdl-p text-xs text-left">
<p className="ztdl-p text-left text-xs">
<Translated
i18nKey="request.disclaimer"
namespace="device"
@@ -95,7 +95,7 @@ export function ConsentScreen({
variant={ButtonVariants.Secondary}
data-testid="deny-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="device.request.deny" namespace="device" />
</Button>
<span className="flex-grow"></span>

View File

@@ -27,7 +27,7 @@ export function CopyToClipboard({ value }: Props) {
<button
id="tooltip-ctc"
type="button"
className=" text-primary-light-500 dark:text-primary-dark-500"
className="text-primary-light-500 dark:text-primary-dark-500"
onClick={() => setCopied(true)}
>
{!copied ? (

View File

@@ -85,7 +85,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) {
onClick={handleSubmit(submitCodeAndContinue)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="verify.submit" namespace="verify" />
</Button>
</div>

View File

@@ -17,7 +17,7 @@ export function DynamicTheme({
}) {
return (
<ThemeWrapper branding={branding}>
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500 px-8 py-12">
<div className="rounded-lg bg-background-light-400 px-8 py-12 dark:bg-background-dark-500">
<div className="mx-auto flex flex-col items-center space-y-4">
<div className="relative flex flex-row items-center justify-center gap-8">
{branding && (

View File

@@ -26,15 +26,15 @@ export const BaseButton = forwardRef<
ref={ref}
disabled={formStatus.pending}
className={clsx(
"flex-1 transition-all cursor-pointer flex flex-row items-center bg-background-light-400 text-text-light-500 dark:bg-background-dark-500 dark:text-text-dark-500 border border-divider-light hover:border-black dark:border-divider-dark hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500 outline-none rounded-md px-4 text-sm",
"flex flex-1 cursor-pointer flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 text-sm text-text-light-500 outline-none transition-all hover:border-black focus:border-primary-light-500 dark:border-divider-dark dark:bg-background-dark-500 dark:text-text-dark-500 hover:dark:border-white focus:dark:border-primary-dark-500",
props.className,
)}
>
<div className="flex-1 justify-between flex items-center gap-4">
<div className="flex-1 flex flex-row items-center">
<div className="flex flex-1 items-center justify-between gap-4">
<div className="flex flex-1 flex-row items-center">
{props.children}
</div>
{formStatus.pending && <Loader2Icon className="w-4 h-4 animate-spin" />}
{formStatus.pending && <Loader2Icon className="h-4 w-4 animate-spin" />}
</div>
</button>
);

View File

@@ -12,7 +12,7 @@ export const SignInWithApple = forwardRef<
return (
<BaseButton {...restProps} ref={ref}>
<div className="h-12 w-12 flex items-center justify-center">
<div className="flex h-12 w-12 items-center justify-center">
<div className="h-6 w-6">
<svg viewBox="0 0 170 170" fill="currentColor">
<title>Apple Logo</title>

View File

@@ -12,13 +12,13 @@ export const SignInWithAzureAd = forwardRef<
return (
<BaseButton {...restProps} ref={ref}>
<div className="h-12 p-[10px] w-12 flex items-center justify-center">
<div className="flex h-12 w-12 items-center justify-center p-[10px]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="21"
viewBox="0 0 21 21"
className="w-full h-full"
className="h-full w-full"
>
<path fill="#f25022" d="M1 1H10V10H1z"></path>
<path fill="#00a4ef" d="M1 11H10V20H1z"></path>

View File

@@ -11,7 +11,7 @@ function GitHubLogo() {
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 1024 1024"
className="h-8 w-8 hidden dark:block"
className="hidden h-8 w-8 dark:block"
>
<path
fill="#fafafa"
@@ -24,7 +24,7 @@ function GitHubLogo() {
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 1024 1024"
className="h-8 w-8 block dark:hidden"
className="block h-8 w-8 dark:hidden"
>
<path
fill="#1B1F23"

View File

@@ -12,7 +12,7 @@ export const SignInWithGitlab = forwardRef<
return (
<BaseButton {...restProps} ref={ref}>
<div className="h-12 w-12 flex items-center justify-center">
<div className="flex h-12 w-12 items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}

View File

@@ -12,7 +12,7 @@ export const SignInWithGoogle = forwardRef<
return (
<BaseButton {...restProps} ref={ref}>
<div className="h-12 w-12 flex items-center justify-center">
<div className="flex h-12 w-12 items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"

View File

@@ -27,12 +27,9 @@ export type TextInputProps = DetailedHTMLProps<
const styles = (error: boolean, disabled: boolean) =>
clsx({
"h-[40px] mb-[2px] rounded p-[7px] bg-input-light-background dark:bg-input-dark-background transition-colors duration-300 grow":
true,
"border border-input-light-border dark:border-input-dark-border hover:border-black hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500":
true,
"focus:outline-none focus:ring-0 text-base text-black dark:text-white placeholder:italic placeholder-gray-700 dark:placeholder-gray-700":
true,
"h-[40px] mb-[2px] rounded p-[7px] bg-input-light-background dark:bg-input-dark-background transition-colors duration-300 grow": true,
"border border-input-light-border dark:border-input-dark-border hover:border-black hover:dark:border-white focus:border-primary-light-500 focus:dark:border-primary-dark-500": true,
"focus:outline-none focus:ring-0 text-base text-black dark:text-white placeholder:italic placeholder-gray-700 dark:placeholder-gray-700": true,
"border border-warn-light-500 dark:border-warn-dark-500 hover:border-warn-light-500 hover:dark:border-warn-dark-500 focus:border-warn-light-500 focus:dark:border-warn-dark-500":
error,
"pointer-events-none text-gray-500 dark:text-gray-800 border border-input-light-border dark:border-input-dark-border hover:border-light-hoverborder hover:dark:border-hoverborder cursor-default":
@@ -60,7 +57,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
return (
<label className="relative flex flex-col text-12px text-input-light-label dark:text-input-dark-label">
<span
className={`leading-3 mb-1 ${
className={`mb-1 leading-3 ${
error ? "text-warn-light-500 dark:text-warn-dark-500" : ""
}`}
>
@@ -81,12 +78,12 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
/>
{suffix && (
<span className="z-30 absolute right-[3px] bottom-[22px] transform translate-y-1/2 bg-background-light-500 dark:bg-background-dark-500 p-2 rounded-sm">
<span className="absolute bottom-[22px] right-[3px] z-30 translate-y-1/2 transform rounded-sm bg-background-light-500 p-2 dark:bg-background-dark-500">
@{suffix}
</span>
)}
<div className="leading-14.5px h-14.5px text-warn-light-500 dark:text-warn-dark-500 flex flex-row items-center text-12px">
<div className="leading-14.5px h-14.5px flex flex-row items-center text-12px text-warn-light-500 dark:text-warn-dark-500">
<span>{error ? error : " "}</span>
</div>

View File

@@ -37,13 +37,13 @@ export function LanguageSwitcher() {
<Listbox value={selected} onChange={handleChange}>
<ListboxButton
className={clsx(
"relative block w-full rounded-lg bg-black/5 dark:bg-white/5 py-1.5 pr-8 pl-3 text-left text-sm/6 text-black dark:text-white",
"relative block w-full rounded-lg bg-black/5 py-1.5 pl-3 pr-8 text-left text-sm/6 text-black dark:bg-white/5 dark:text-white",
"focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25",
)}
>
{selected.name}
<ChevronDownIcon
className="group pointer-events-none absolute top-2.5 right-2.5 size-4"
className="group pointer-events-none absolute right-2.5 top-2.5 size-4"
aria-hidden="true"
/>
</ListboxButton>
@@ -51,7 +51,7 @@ export function LanguageSwitcher() {
anchor="bottom"
transition
className={clsx(
"w-[var(--button-width)] rounded-xl border border-black/5 dark:border-white/5 bg-background-light-500 dark:bg-background-dark-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none",
"w-[var(--button-width)] rounded-xl border border-black/5 bg-background-light-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5 dark:bg-background-dark-500",
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0",
)}
>
@@ -59,7 +59,7 @@ export function LanguageSwitcher() {
<ListboxOption
key={lang.code}
value={lang}
className="group flex cursor-default items-center gap-2 rounded-lg py-1.5 px-3 select-none data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10"
className="group flex cursor-default select-none items-center gap-2 rounded-lg px-3 py-1.5 data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10"
>
<CheckIcon className="invisible size-4 group-data-[selected]:visible" />
<div className="text-sm/6 text-black dark:text-white">

View File

@@ -100,7 +100,7 @@ export function LDAPUsernamePasswordForm({ idpId, link }: Props) {
onClick={handleSubmit(submitUsernamePassword)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="submit" namespace="ldap" />
</Button>
</div>

View File

@@ -220,14 +220,14 @@ export function LoginOTP({
{["email", "sms"].includes(method) && (
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
<span className="mr-auto flex-1 text-left">
<Translated i18nKey="verify.noCodeReceived" namespace="otp" />
</span>
<button
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
onClick={() => {
setLoading(true);
updateSessionForOTPChallenge()
@@ -275,7 +275,7 @@ export function LoginOTP({
})}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="verify.submit" namespace="otp" />
</Button>
</div>

View File

@@ -271,7 +271,7 @@ export function LoginPasskey({
}}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="verify.submit" namespace="passkey" />
</Button>
</div>

View File

@@ -19,7 +19,7 @@ const check = (
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg"
className="las la-check mr-2 h-6 w-6 text-lg text-green-500 dark:text-green-500"
role="img"
>
<title>Matches</title>
@@ -32,7 +32,7 @@ const check = (
);
const cross = (
<svg
className="w-6 h-6 las la-times text-warn-light-500 dark:text-warn-dark-500 mr-2 text-lg"
className="las la-times mr-2 h-6 w-6 text-lg text-warn-light-500 dark:text-warn-dark-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"

View File

@@ -124,7 +124,7 @@ export function PasswordForm({
/>
{!loginSettings?.hidePasswordReset && (
<button
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
onClick={() => resetPasswordAndContinue()}
type="button"
disabled={loading}
@@ -167,7 +167,7 @@ export function PasswordForm({
onClick={handleSubmit(submitPassword)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="verify.submit" namespace="password" />
</Button>
</div>

View File

@@ -23,7 +23,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
return (
<>
<p className="flex flex-row items-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 text-sm">
<p className="mt-4 flex flex-row items-center text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500">
<Translated i18nKey="agreeTo" namespace="register" />
{legal?.helpLink && (
<span>
@@ -34,7 +34,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="ml-1 w-5 h-5"
className="ml-1 h-5 w-5"
>
<path
strokeLinecap="round"

View File

@@ -96,7 +96,7 @@ export function RegisterFormIDPIncomplete({
return (
<form className="w-full">
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="mb-4 grid grid-cols-2 gap-4">
<div className="">
<TextInput
type="firstname"
@@ -147,7 +147,7 @@ export function RegisterFormIDPIncomplete({
onClick={handleSubmit(submitAndRegister)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="submit" namespace="register" />
</Button>
</div>

View File

@@ -124,7 +124,7 @@ export function RegisterForm({
const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false);
return (
<form className="w-full">
<div className="grid grid-cols-2 gap-4 mb-4">
<div className="mb-4 grid grid-cols-2 gap-4">
<div className="">
<TextInput
type="firstname"
@@ -170,7 +170,7 @@ export function RegisterForm({
loginSettings.allowUsernamePassword &&
loginSettings.passkeysType == PasskeysType.ALLOWED && (
<>
<p className="mt-4 ztdl-p mb-6 block text-left">
<p className="ztdl-p mb-6 mt-4 block text-left">
<Translated i18nKey="selectMethod" namespace="register" />
</p>
@@ -211,14 +211,14 @@ export function RegisterForm({
const usePasswordToContinue: boolean =
loginSettings?.allowUsernamePassword &&
loginSettings?.passkeysType == PasskeysType.ALLOWED
? !!!(selected === methods[0]) // choose selection if both available
? !(selected === methods[0]) // choose selection if both available
: !!loginSettings?.allowUsernamePassword; // if password is chosen
// set password as default if only password is allowed
return submitAndContinue(values, usePasswordToContinue);
})}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="submit" namespace="register" />
</Button>
</div>

View File

@@ -211,7 +211,7 @@ export function RegisterPasskey({
onClick={handleSubmit(submitRegisterAndContinue)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="set.submit" namespace="passkey" />
</Button>
</div>

View File

@@ -216,7 +216,7 @@ export function RegisterU2f({
onClick={submitRegisterAndContinue}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="set.submit" namespace="u2f" />
</Button>
</div>

View File

@@ -15,7 +15,7 @@ export function SelfServiceMenu({ sessionId }: { sessionId: string }) {
// }
return (
<div className="w-full flex flex-col space-y-2">
<div className="flex w-full flex-col space-y-2">
{list.map((menuitem, index) => {
return (
<SelfServiceItem
@@ -34,7 +34,7 @@ const SelfServiceItem = ({ name, link }: { name: string; link: string }) => {
<Link
prefetch={false}
href={link}
className="w-full group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
className="group flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
>
{name}
</Link>

View File

@@ -52,7 +52,7 @@ export function SessionClearItem({
reload();
});
}}
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
>
<div className="pr-4">
<Avatar
@@ -64,11 +64,11 @@ export function SessionClearItem({
<div className="flex flex-col items-start overflow-hidden">
<span className="">{session.factors?.user?.displayName}</span>
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
{session.factors?.user?.loginName}
</span>
{valid ? (
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
{verifiedAt && (
<Translated
i18nKey="verfiedAt"
@@ -79,7 +79,7 @@ export function SessionClearItem({
</span>
) : (
verifiedAt && (
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
expired{" "}
{session.expirationDate &&
moment(timestampDate(session.expirationDate)).fromNow()}
@@ -90,14 +90,14 @@ export function SessionClearItem({
<span className="flex-grow"></span>
<div className="relative flex flex-row items-center">
<div className="mr-6 px-2 py-[2px] text-xs hidden group-hover:block transition-all text-warn-light-500 dark:text-warn-dark-500 bg-[#ff0000]/10 dark:bg-[#ff0000]/10 rounded-full flex items-center justify-center">
<div className="mr-6 flex hidden items-center justify-center rounded-full bg-[#ff0000]/10 px-2 py-[2px] text-xs text-warn-light-500 transition-all group-hover:block dark:bg-[#ff0000]/10 dark:text-warn-dark-500">
<Translated i18nKey="clear" namespace="logout" />
</div>
{valid ? (
<div className="absolute h-2 w-2 bg-green-500 rounded-full mx-2 transform right-0 transition-all"></div>
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all"></div>
) : (
<div className="absolute h-2 w-2 bg-red-500 rounded-full mx-2 transform right-0 transition-all"></div>
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all"></div>
)}
</div>
</button>

View File

@@ -102,7 +102,7 @@ export function SessionItem({
}
}
}}
className="group flex flex-row items-center bg-background-light-400 dark:bg-background-dark-400 border border-divider-light hover:shadow-lg dark:hover:bg-white/10 py-2 px-4 rounded-md transition-all"
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
>
<div className="pr-4">
<Avatar
@@ -114,16 +114,16 @@ export function SessionItem({
<div className="flex flex-col items-start overflow-hidden">
<span className="">{session.factors?.user?.displayName}</span>
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
{session.factors?.user?.loginName}
</span>
{valid ? (
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
</span>
) : (
verifiedAt && (
<span className="text-xs opacity-80 text-ellipsis">
<span className="text-ellipsis text-xs opacity-80">
expired{" "}
{session.expirationDate &&
moment(timestampDate(session.expirationDate)).fromNow()}
@@ -135,13 +135,13 @@ export function SessionItem({
<span className="flex-grow"></span>
<div className="relative flex flex-row items-center">
{valid ? (
<div className="absolute h-2 w-2 bg-green-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-green-500 transition-all group-hover:right-6"></div>
) : (
<div className="absolute h-2 w-2 bg-red-500 rounded-full mx-2 transform right-0 group-hover:right-6 transition-all"></div>
<div className="absolute right-0 mx-2 h-2 w-2 transform rounded-full bg-red-500 transition-all group-hover:right-6"></div>
)}
<XCircleIcon
className="hidden group-hover:block h-5 w-5 transition-all opacity-50 hover:opacity-100"
className="hidden h-5 w-5 opacity-50 transition-all hover:opacity-100 group-hover:block"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();

View File

@@ -188,18 +188,18 @@ export function SetPasswordForm({
return (
<form className="w-full">
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
{codeRequired && (
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
<span className="mr-auto flex-1 text-left">
<Translated i18nKey="set.noCodeReceived" namespace="password" />
</span>
<button
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
@@ -277,7 +277,7 @@ export function SetPasswordForm({
onClick={handleSubmit(submitPassword)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="set.submit" namespace="password" />
</Button>
</div>

View File

@@ -108,7 +108,7 @@ export function SetRegisterPasswordForm({
return (
<form className="w-full">
<div className="pt-4 grid grid-cols-1 gap-4 mb-4">
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
<div className="">
<TextInput
type="password"
@@ -161,7 +161,7 @@ export function SetRegisterPasswordForm({
onClick={handleSubmit(submitRegister)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}{" "}
{loading && <Spinner className="mr-2 h-5 w-5" />}{" "}
<Translated i18nKey="password.submit" namespace="register" />
</Button>
</div>

View File

@@ -76,8 +76,8 @@ export function SignInWithIdp({
};
return (
<div className="flex flex-col w-full space-y-2 text-sm">
<p className="text-center ztdl-p">
<div className="flex w-full flex-col space-y-2 text-sm">
<p className="ztdl-p text-center">
<Translated i18nKey="orSignInWith" namespace="idp" />
</p>
{!!identityProviders.length && identityProviders?.map(renderIDPButton)}

View File

@@ -2,7 +2,7 @@ import { ReactNode } from "react";
export function Skeleton({ children }: { children?: ReactNode }) {
return (
<div className="skeleton py-12 px-8 rounded-lg bg-background-light-600 dark:bg-background-dark-600 flex flex-row items-center justify-center">
<div className="skeleton flex flex-row items-center justify-center rounded-lg bg-background-light-600 px-8 py-12 dark:bg-background-dark-600">
{children}
</div>
);

View File

@@ -4,7 +4,7 @@ export const Spinner: FC<{ className?: string }> = ({ className = "" }) => {
return (
<svg
role="status"
className={`${className} inline-block animate-spin fill-primary-light-500 dark:fill-primary-dark-500 text-black/10 dark:text-white/10`}
className={`${className} inline-block animate-spin fill-primary-light-500 text-black/10 dark:fill-primary-dark-500 dark:text-white/10`}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"

View File

@@ -16,8 +16,7 @@ export type StateBadgeProps = {
const getBadgeClasses = (state: BadgeState, evenPadding: boolean) =>
clsx({
"w-fit border-box h-18.5px flex flex-row items-center whitespace-nowrap tracking-wider leading-4 items-center justify-center px-2 py-2px text-12px rounded-full shadow-sm":
true,
"w-fit border-box h-18.5px flex flex-row items-center whitespace-nowrap tracking-wider leading-4 items-center justify-center px-2 py-2px text-12px rounded-full shadow-sm": true,
"bg-state-success-light-background text-state-success-light-color dark:bg-state-success-dark-background dark:text-state-success-dark-color ":
state === BadgeState.Success,
"bg-state-neutral-light-background text-state-neutral-light-color dark:bg-state-neutral-dark-background dark:text-state-neutral-dark-color":

View File

@@ -23,7 +23,7 @@ export const Tab = ({
return (
<Link
href={href}
className={clsx("mt-2 mr-2 rounded-lg px-3 py-1 text-sm font-medium", {
className={clsx("mr-2 mt-2 rounded-lg px-3 py-1 text-sm font-medium", {
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white":
!isActive,
"bg-blue-500 text-white": isActive,

View File

@@ -21,23 +21,23 @@ export function Theme() {
return (
<div
className={`h-fit relative grid grid-cols-2 rounded-full border border-divider-light dark:border-divider-dark p-1`}
className={`relative grid h-fit grid-cols-2 rounded-full border border-divider-light p-1 dark:border-divider-dark`}
>
<button
className={`h-8 w-8 rounded-full flex flex-row items-center justify-center hover:opacity-100 transition-all ${
className={`flex h-8 w-8 flex-row items-center justify-center rounded-full transition-all hover:opacity-100 ${
isDark ? "bg-black/10 dark:bg-white/10" : "opacity-60"
}`}
onClick={() => setTheme("dark")}
>
<MoonIcon className="h-4 w-4 flex-shrink-0 text-xl rounded-full" />
<MoonIcon className="h-4 w-4 flex-shrink-0 rounded-full text-xl" />
</button>
<button
className={`h-8 w-8 rounded-full flex flex-row items-center justify-center hover:opacity-100 transition-all ${
className={`flex h-8 w-8 flex-row items-center justify-center rounded-full transition-all hover:opacity-100 ${
!isDark ? "bg-black/10 dark:bg-white/10" : "opacity-60"
}`}
onClick={() => setTheme("light")}
>
<SunIcon className="h-6 w-6 flex-shrink-0 text-xl rounded-full" />
<SunIcon className="h-6 w-6 flex-shrink-0 rounded-full text-xl" />
</button>
</div>
);

View File

@@ -105,14 +105,14 @@ export function TotpRegister({
}
return (
<div className="flex flex-col items-center ">
<div className="flex flex-col items-center">
{uri && (
<>
<QRCodeSVG
className="rounded-md w-40 h-40 p-2 bg-white my-4"
className="my-4 h-40 w-40 rounded-md bg-white p-2"
value={uri}
/>
<div className="mb-4 w-96 flex text-sm my-2 border rounded-lg px-4 py-2 pr-2 border-divider-light dark:border-divider-dark">
<div className="my-2 mb-4 flex w-96 rounded-lg border border-divider-light px-4 py-2 pr-2 text-sm dark:border-divider-dark">
<Link href={uri} target="_blank" className="flex-1 overflow-x-auto">
{uri}
</Link>
@@ -145,7 +145,7 @@ export function TotpRegister({
onClick={handleSubmit(continueWithCode)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="set.submit" namespace="otp" />
</Button>
</div>

View File

@@ -42,14 +42,14 @@ export function UserAvatar({
loginName={loginName ?? ""}
/>
</div>
<span className="ml-4 pr-4 text-14px max-w-[250px] text-ellipsis overflow-hidden">
<span className="ml-4 max-w-[250px] overflow-hidden text-ellipsis pr-4 text-14px">
{loginName}
</span>
<span className="flex-grow"></span>
{showDropdown && (
<Link
href={"/accounts?" + params}
className="ml-4 flex items-center justify-center p-1 hover:bg-black/10 dark:hover:bg-white/10 rounded-full mr-1 transition-all"
className="ml-4 mr-1 flex items-center justify-center rounded-full p-1 transition-all hover:bg-black/10 dark:hover:bg-white/10"
>
<ChevronDownIcon className="h-4 w-4" />
</Link>

View File

@@ -110,7 +110,7 @@ export function UsernameForm({
/>
{allowRegister && (
<button
className="transition-all text-sm hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
onClick={() => {
const registerParams = new URLSearchParams();
if (organization) {
@@ -147,7 +147,7 @@ export function UsernameForm({
disabled={loading || !formState.isValid}
onClick={handleSubmit((e) => submitLoginName(e, organization))}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="submit" namespace="loginname" />
</Button>
</div>

View File

@@ -114,14 +114,14 @@ export function VerifyForm({
<form className="w-full">
<Alert type={AlertType.INFO}>
<div className="flex flex-row">
<span className="flex-1 mr-auto text-left">
<span className="mr-auto flex-1 text-left">
<Translated i18nKey="verify.noCodeReceived" namespace="verify" />
</span>
<button
aria-label="Resend Code"
disabled={loading}
type="button"
className="ml-4 text-primary-light-500 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 hover:text-primary-light-400 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
@@ -158,7 +158,7 @@ export function VerifyForm({
onClick={handleSubmit(fcn)}
data-testid="submit-button"
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
{loading && <Spinner className="mr-2 h-5 w-5" />}
<Translated i18nKey="verify.submit" namespace="verify" />
</Button>
</div>

View File

@@ -314,7 +314,7 @@ export async function getMostRecentCookieWithLoginname<T>({
if (stringifiedCookie?.value) {
const sessions: SessionCookie<T>[] = JSON.parse(stringifiedCookie?.value);
let filtered = sessions.filter((cookie) => {
return !!loginName ? cookie.loginName === loginName : true;
return loginName ? cookie.loginName === loginName : true;
});
if (organization) {

View File

@@ -145,7 +145,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) {
// if no name is provided, try to generate one from the user agent
let passkeyName = command.passkeyName;
if (!!!passkeyName) {
if (!passkeyName) {
const headersList = await headers();
const userAgentStructure = { headers: headersList };
const { browser, device, os } = userAgent(userAgentStructure);

View File

@@ -67,7 +67,7 @@ export async function verifyU2F(command: VerifyU2FCommand) {
}
let passkeyName = command.passkeyName;
if (!!!passkeyName) {
if (!passkeyName) {
const headersList = await headers();
const userAgentStructure = { headers: headersList };
const { browser, device, os } = userAgent(userAgentStructure);

View File

@@ -8,11 +8,11 @@
@layer base {
h1,
.ztdl-h1 {
@apply text-2xl text-center;
@apply text-center text-2xl;
}
.ztdl-p {
@apply text-sm text-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 text-center;
@apply text-center text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500;
}
}

View File

@@ -9,14 +9,26 @@ let themeColors = {
link: { light: { contrast: {} }, dark: { contrast: {} } },
};
const shades = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900"];
const shades = [
"50",
"100",
"200",
"300",
"400",
"500",
"600",
"700",
"800",
"900",
];
const themes = ["light", "dark"];
const types = ["background", "primary", "warn", "text", "link"];
types.forEach((type) => {
themes.forEach((theme) => {
shades.forEach((shade) => {
themeColors[type][theme][shade] = `var(--theme-${theme}-${type}-${shade})`;
themeColors[type][theme][shade] =
`var(--theme-${theme}-${type}-${shade})`;
themeColors[type][theme][`contrast-${shade}`] =
`var(--theme-${theme}-${type}-contrast-${shade})`;
themeColors[type][theme][`secondary-${shade}`] =

View File

@@ -0,0 +1 @@
import "@testing-library/jest-dom/vitest";

View File

@@ -7,6 +7,6 @@ export default defineConfig({
test: {
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
environment: "jsdom",
setupFiles: ["@testing-library/jest-dom/vitest"],
setupFiles: ["./test-setup.ts"],
},
});

2
login/test-setup.ts Normal file
View File

@@ -0,0 +1,2 @@
// Test setup file to handle jest-dom configuration
import "@testing-library/jest-dom";