initial setup, ztdl-login, core

This commit is contained in:
Max Peintner
2023-04-03 13:39:51 +02:00
parent c1c9ccbf03
commit f9299ad990
100 changed files with 126387 additions and 81 deletions

View File

@@ -0,0 +1,56 @@
'use client';
import React from 'react';
import { usePathname } from 'next/navigation';
export function AddressBar() {
const pathname = usePathname();
return (
<div className="flex items-center space-x-2 p-3.5 lg:px-5 lg:py-3">
<div className="text-gray-600">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="flex space-x-1 text-sm font-medium">
<div>
<span className="px-2 text-gray-500">acme.com</span>
</div>
{pathname ? (
<>
<span className="text-gray-600">/</span>
{pathname
.split('/')
.slice(1)
.map((segment) => {
return (
<React.Fragment key={segment}>
<span>
<span
key={segment}
className="animate-[highlight_1s_ease-in-out_1] rounded-full px-1.5 py-0.5 text-gray-100"
>
{segment}
</span>
</span>
<span className="text-gray-600">/</span>
</React.Fragment>
);
})}
</>
) : null}
</div>
</div>
);
}

View File

@@ -0,0 +1,82 @@
import clsx from 'clsx';
import React from 'react';
const Label = ({
children,
animateRerendering,
color,
}: {
children: React.ReactNode;
animateRerendering?: boolean;
color?: 'default' | 'pink' | 'blue' | 'violet' | 'cyan' | 'orange';
}) => {
return (
<div
className={clsx('rounded-full px-1.5 shadow-[0_0_1px_3px_black]', {
'bg-gray-800 text-gray-500': color === 'default',
'bg-vercel-pink text-pink-100': color === 'pink',
'bg-vercel-blue text-blue-100': color === 'blue',
'bg-vercel-cyan text-cyan-100': color === 'cyan',
'bg-vercel-violet text-violet-100': color === 'violet',
'bg-vercel-orange text-orange-100': color === 'orange',
'animate-[highlight_1s_ease-in-out_1]': animateRerendering,
})}
>
{children}
</div>
);
};
export const Boundary = ({
children,
labels = ['children'],
size = 'default',
color = 'default',
animateRerendering = true,
}: {
children: React.ReactNode;
labels?: string[];
size?: 'small' | 'default';
color?: 'default' | 'pink' | 'blue' | 'violet' | 'cyan' | 'orange';
animateRerendering?: boolean;
}) => {
return (
<div
className={clsx('relative rounded-lg border border-dashed', {
'p-3 lg:p-5': size === 'small',
'p-4 lg:p-9': size === 'default',
'border-gray-700': color === 'default',
'border-vercel-pink': color === 'pink',
'border-vercel-blue': color === 'blue',
'border-vercel-cyan': color === 'cyan',
'border-vercel-violet': color === 'violet',
'border-vercel-orange': color === 'orange',
'animate-[rerender_1s_ease-in-out_1] text-vercel-pink':
animateRerendering,
})}
>
<div
className={clsx(
'absolute -top-2.5 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest',
{
'left-3 lg:left-5': size === 'small',
'left-4 lg:left-9': size === 'default',
},
)}
>
{labels.map((label) => {
return (
<Label
key={label}
color={color}
animateRerendering={animateRerendering}
>
{label}
</Label>
);
})}
</div>
{children}
</div>
);
};

80
apps/login/ui/Button.tsx Normal file
View File

@@ -0,0 +1,80 @@
import clsx from 'clsx';
import React, {
ButtonHTMLAttributes,
DetailedHTMLProps,
forwardRef,
} from 'react';
export enum ButtonSizes {
Small = 'Small',
Large = 'Large',
}
export enum ButtonVariants {
Primary = 'Primary',
Secondary = 'Secondary',
Destructive = 'Destructive',
}
export enum ButtonColors {
Neutral = 'Neutral',
Primary = 'Primary',
Warn = 'Warn',
}
export type ButtonProps = DetailedHTMLProps<
ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> & {
size?: ButtonSizes;
variant?: ButtonVariants;
color?: ButtonColors;
};
export const getButtonClasses = (
size: ButtonSizes,
variant: ButtonVariants,
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,
'shadow hover:shadow-xl active:shadow-xl disabled:border-none disabled:bg-gray-300 disabled:text-gray-600 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 dark:text-primary-dark-contrast':
variant === ButtonVariants.Primary && color !== ButtonColors.Warn,
'bg-warn-light-500 dark:bg-warn-dark-500 hover:bg-warn-light-400 hover:dark:bg-warn-dark-400 text-white dark:text-white':
variant === ButtonVariants.Primary && color === ButtonColors.Warn,
'border border-button-light-border dark:border-button-dark-border text-gray-950 hover:bg-gray-500 hover:bg-opacity-20 hover:dark:bg-white hover:dark:bg-opacity-10 focus:bg-gray-500 focus:bg-opacity-20 focus:dark:bg-white focus:dark:bg-opacity-10 dark:text-white disabled:text-gray-600 disabled:hover:bg-transparent disabled:dark:hover:bg-transparent disabled:cursor-not-allowed disabled:dark:text-gray-900':
variant === ButtonVariants.Secondary,
'border border-button-light-border dark:border-button-dark-border text-warn-light-500 dark:text-warn-dark-500 hover:bg-warn-light-500 hover:bg-opacity-10 dark:hover:bg-warn-light-500 dark:hover:bg-opacity-10 focus:bg-warn-light-500 focus:bg-opacity-20 dark:focus:bg-warn-light-500 dark:focus:bg-opacity-20':
color === ButtonColors.Warn && variant !== ButtonVariants.Primary,
'px-16 py-2': size === ButtonSizes.Large,
'px-4 h-[36px]': size === ButtonSizes.Small,
});
// eslint-disable-next-line react/display-name
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
children,
className = '',
variant = ButtonVariants.Primary,
size = ButtonSizes.Small,
color = ButtonColors.Primary,
...props
},
ref,
) => {
return (
<button
type="button"
ref={ref}
className={`${getButtonClasses(size, variant, color)} ${className}`}
{...props}
>
{children}
</button>
);
},
);

25
apps/login/ui/CountUp.tsx Normal file
View File

@@ -0,0 +1,25 @@
'use client';
import { useCountUp } from 'use-count-up';
const CountUp = ({
start,
end,
duration = 1,
}: {
start: number;
end: number;
duration?: number;
}) => {
const { value } = useCountUp({
isCounting: true,
end,
start,
duration,
decimalPlaces: 1,
});
return <span>{value}</span>;
};
export default CountUp;

View File

@@ -0,0 +1,32 @@
// Default <head> tags we want shared across the app
export function DefaultTags() {
return (
<>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="/favicon/apple-touch-icon.png"
rel="apple-touch-icon"
sizes="180x180"
/>
<link
href="/favicon/favicon-32x32.png"
rel="icon"
sizes="32x32"
type="image/png"
/>
<link
href="/favicon/favicon-16x16.png"
rel="icon"
sizes="16x16"
type="image/png"
/>
<link href="/favicon/site.webmanifest" rel="manifest" />
<link
color="#000000"
href="/favicon/safari-pinned-tab.svg"
rel="mask-icon"
/>
<link href="/favicon/favicon.ico" rel="shortcut icon" />
</>
);
}

View File

@@ -0,0 +1,20 @@
import { ArrowRightIcon } from '@heroicons/react/24/solid';
export const ExternalLink = ({
children,
href,
}: {
children: React.ReactNode;
href: string;
}) => {
return (
<a
href={href}
className="inline-flex space-x-2 rounded-lg bg-gray-700 px-3 py-1 text-sm font-medium text-gray-100 hover:bg-gray-500 hover:text-white"
>
<div>{children}</div>
<ArrowRightIcon className="block w-4" />
</a>
);
};

41
apps/login/ui/Footer.tsx Normal file
View File

@@ -0,0 +1,41 @@
'use client';
export default function Footer({
reactVersion,
nextVersion,
}: {
reactVersion: string;
nextVersion: string;
}) {
return (
<div className="col-start-2 col-end-4 mt-28 flex items-center justify-between">
<style jsx>
{`
.power-by {
color: rgb(82 82 91);
display: inline-flex;
align-items: center;
}
.power-by-text {
margin-right: 0.25rem;
}
`}
</style>
<span className="power-by">
<span className="power-by-text">Powered by</span>
<svg height="20" viewBox="0 0 283 64" fill="none">
<path
fill="currentColor"
d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z"
></path>
</svg>
</span>
<div className="flex space-x-6 text-sm text-gray-600">
<div>React: {reactVersion}</div>
<div>Next: {nextVersion}</div>
</div>
</div>
);
}

101
apps/login/ui/GlobalNav.tsx Normal file
View File

@@ -0,0 +1,101 @@
'use client';
import { demos, type Item } from '#/lib/demos';
import '#/styles/globals.css';
import { ZitadelLogo } from '#/ui/ZitadelLogo';
import Link from 'next/link';
import { useSelectedLayoutSegment } from 'next/navigation';
import clsx from 'clsx';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/solid';
import { useState } from 'react';
export function GlobalNav() {
const [isOpen, setIsOpen] = useState(false);
const close = () => setIsOpen(false);
return (
<div className="fixed top-0 z-10 flex w-full flex-col border-b border-gray-800 bg-background-dark-600 lg:bottom-0 lg:z-auto lg:w-72 lg:border-r lg:border-gray-800">
<div className="flex h-14 items-center py-4 px-4 lg:h-auto">
<Link
href="/"
className="group flex w-full items-center space-x-2.5"
onClick={close}
>
<div className="h-8 w-8">
<ZitadelLogo />
</div>
<h2 className="font-medium tracking-wide text-gray-300 group-hover:text-gray-50">
Login Playground <span className="Work in progress">(WIP)</span>
</h2>
</Link>
</div>
<button
type="button"
className="group absolute right-0 top-0 flex h-14 items-center space-x-2 px-4 lg:hidden"
onClick={() => setIsOpen(!isOpen)}
>
<div className="font-medium text-gray-100 group-hover:text-gray-400">
Menu
</div>
{isOpen ? (
<XMarkIcon className="block w-6 text-gray-300" />
) : (
<Bars3Icon className="block w-6 text-gray-300" />
)}
</button>
<div
className={clsx('overflow-y-auto lg:static lg:block', {
'fixed inset-x-0 bottom-0 top-14 mt-px bg-black': isOpen,
hidden: !isOpen,
})}
>
<nav className="space-y-6 px-2 py-5">
{demos.map((section) => {
return (
<div key={section.name}>
<div className="mb-2 px-3 text-xs font-semibold uppercase tracking-wider text-gray-600">
<div>{section.name}</div>
</div>
<div className="space-y-1">
{section.items.map((item) => (
<GlobalNavItem key={item.slug} item={item} close={close} />
))}
</div>
</div>
);
})}
</nav>
</div>
</div>
);
}
function GlobalNavItem({
item,
close,
}: {
item: Item;
close: () => false | void;
}) {
const segment = useSelectedLayoutSegment();
const isActive = item.slug === segment;
return (
<Link
onClick={close}
href={`/${item.slug}`}
className={clsx(
'block rounded-md px-3 py-2 text-sm font-medium hover:text-gray-300',
{
'text-gray-500 hover:bg-gray-800': !isActive,
'text-gray-200': isActive,
},
)}
>
{item.name}
</Link>
);
}

43
apps/login/ui/Header.tsx Normal file
View File

@@ -0,0 +1,43 @@
'use client';
import styled from 'styled-components';
const HeadContainer = styled.header`
position: relative;
height: 64px;
align-items: center;
padding: 0px 8px;
margin-bottom: 48px;
display: flex;
border: 0 solid #e5e7eb;
color: rgb(244 244 245);
grid-column-start: 2;
grid-column-end: 4;
`;
const Title = styled.span`
margin: 0 8px;
`;
const NextJsLogo = (props: any) => (
<svg
version="1.1"
viewBox="0 0 148 90"
xmlnsXlink="http://www.w3.org/1999/xlink"
{...props}
>
<path
d="M34.992 23.495h27.855v2.219H37.546v16.699h23.792v2.219H37.546v18.334h25.591v2.219H34.992v-41.69zm30.35 0h2.96l13.115 18.334 13.405-18.334L113.055.207 83.1 43.756l15.436 21.429H95.46L81.417 45.683 67.316 65.185h-3.018L79.85 43.756 65.343 23.495zm34.297 2.219v-2.219h31.742v2.219h-14.623v39.47h-2.554v-39.47H99.64zM.145 23.495h3.192l44.011 66.003L29.16 65.185 2.814 26.648l-.116 38.537H.145v-41.69zm130.98 38.801c-.523 0-.914-.405-.914-.928 0-.524.391-.929.913-.929.528 0 .913.405.913.929 0 .523-.385.928-.913.928zm2.508-2.443H135c.019.742.56 1.24 1.354 1.24.888 0 1.391-.535 1.391-1.539v-6.356h1.391v6.362c0 1.808-1.043 2.849-2.77 2.849-1.62 0-2.732-1.01-2.732-2.556zm7.322-.08h1.379c.118.853.95 1.395 2.149 1.395 1.117 0 1.937-.58 1.937-1.377 0-.685-.521-1.097-1.708-1.377l-1.155-.28c-1.62-.38-2.36-1.166-2.36-2.487 0-1.602 1.304-2.668 3.26-2.668 1.82 0 3.15 1.066 3.23 2.58h-1.354c-.13-.828-.85-1.346-1.894-1.346-1.1 0-1.832.53-1.832 1.34 0 .642.472 1.01 1.64 1.284l.987.243c1.838.43 2.596 1.178 2.596 2.53 0 1.72-1.33 2.799-3.453 2.799-1.987 0-3.323-1.029-3.422-2.637z"
fillRule="nonzero"
></path>
</svg>
);
export default function Header() {
return (
<HeadContainer>
<NextJsLogo height={40} fill={`rgb(244 244 245)`} />
<Title>The React Framework</Title>
</HeadContainer>
);
}

92
apps/login/ui/Input.tsx Normal file
View File

@@ -0,0 +1,92 @@
'use client';
import { CheckCircleIcon } from '@heroicons/react/24/solid';
import clsx from 'clsx';
import React, {
ChangeEvent,
DetailedHTMLProps,
forwardRef,
InputHTMLAttributes,
ReactNode,
} from 'react';
export type TextInputProps = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> & {
label: string;
placeholder?: string;
defaultValue?: string;
error?: string | ReactNode;
success?: string | ReactNode;
disabled?: boolean;
onChange?: (value: ChangeEvent<HTMLInputElement>) => void;
onBlur?: (value: ChangeEvent<HTMLInputElement>) => void;
};
const styles = (error: boolean, disabled: boolean) =>
clsx({
'h-40px mb-2px rounded p-2 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-gray-700 dark:border-gray-900 border-opacity-30 dark:border-opacity-30 hover:border-gray-700 hover:dark:border-gray-900 hover:border-opacity-30 hover:dark:border-opacity-30 cursor-default':
disabled,
});
// eslint-disable-next-line react/display-name
export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
(
{
label,
placeholder,
defaultValue,
required = false,
error,
disabled,
success,
onChange,
onBlur,
...props
},
ref,
) => {
return (
<label className="flex flex-col text-12px text-input-light-label dark:text-input-dark-label">
<span
className={`leading-14.5px mb-1 ${
error ? 'text-warn-light-500 dark:text-warn-dark-500' : ''
}`}
>
{label} {required && '*'}
</span>
<input
ref={ref}
className={styles(!!error, !!disabled)}
defaultValue={defaultValue}
required={required}
disabled={disabled}
placeholder={placeholder}
autoComplete={props.autoComplete ?? 'off'}
onChange={(e) => onChange && onChange(e)}
onBlur={(e) => onBlur && onBlur(e)}
{...props}
/>
<div className="leading-14.5px h-14.5px text-warn-light-500 dark:text-warn-dark-500 flex flex-row items-center text-12px">
<span>{error ? error : ' '}</span>
</div>
{success && (
<div className="text-md mt-1 flex flex-row items-center text-green-500">
<CheckCircleIcon className="h-4 w-4" />
<span className="ml-1">{success}</span>
</div>
)}
</label>
);
},
);

View File

@@ -0,0 +1,64 @@
'use client';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/solid';
import clsx from 'clsx';
import React from 'react';
const MobileNavContext = React.createContext<
[boolean, React.Dispatch<React.SetStateAction<boolean>>] | undefined
>(undefined);
export function MobileNavContextProvider({
children,
}: {
children: React.ReactNode;
}) {
const [isOpen, setIsOpen] = React.useState(false);
return (
<MobileNavContext.Provider value={[isOpen, setIsOpen]}>
{children}
</MobileNavContext.Provider>
);
}
export function useMobileNavToggle() {
const context = React.useContext(MobileNavContext);
if (context === undefined) {
throw new Error(
'useMobileNavToggle must be used within a MobileNavContextProvider',
);
}
return context;
}
export function MobileNavToggle({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useMobileNavToggle();
return (
<>
<button
type="button"
className="group absolute right-0 top-0 flex h-14 items-center space-x-2 px-4 lg:hidden"
onClick={() => setIsOpen(!isOpen)}
>
<div className="font-medium text-gray-100 group-hover:text-gray-400">
Menu
</div>
{isOpen ? (
<XMarkIcon className="block w-6 text-gray-300" />
) : (
<Bars3Icon className="block w-6 text-gray-300" />
)}
</button>
<div
className={clsx('overflow-y-auto lg:static lg:block', {
'fixed inset-x-0 bottom-0 top-14 bg-gray-900': isOpen,
hidden: !isOpen,
})}
>
{children}
</div>
</>
);
}

View File

@@ -0,0 +1,102 @@
import {
lowerCaseValidator,
numberValidator,
symbolValidator,
upperCaseValidator,
} from '../utils/validators';
import { ClientError } from 'nice-grpc';
const fetcher = (url: string) =>
fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => {
if (res.ok) {
return res.json();
} else {
return res.json().then((error) => {
throw error;
});
}
})
.then((resp) => resp.policy);
type Props = {
password: string;
equals: boolean;
isValid: (valid: boolean) => void;
isMe?: boolean;
userId?: string;
};
const check = (
<i className="las la-check text-state-success-light-color dark:text-state-success-dark-color mr-4 text-lg"></i>
);
const cross = (
<i className="las la-times text-warn-light-500 dark:text-warn-dark-500 mr-4 text-lg"></i>
);
const desc =
'text-14px leading-4 text-input-light-label dark:text-input-dark-label';
export default function PasswordComplexityPolicy({
password,
equals,
isValid,
isMe,
userId,
}: Props) {
// const { data: policy } = useSWR<Policy, ClientError>(
// `/api/user/passwordpolicy/${isMe ? 'me' : userId}`,
// fetcher,
// );
// if (policy) {
// const hasMinLength = password?.length >= policy.minLength;
// const hasSymbol = symbolValidator(password);
// const hasNumber = numberValidator(password);
// const hasUppercase = upperCaseValidator(password);
// const hasLowercase = lowerCaseValidator(password);
// const policyIsValid =
// (policy.hasLowercase ? hasLowercase : true) &&
// (policy.hasNumber ? hasNumber : true) &&
// (policy.hasUppercase ? hasUppercase : true) &&
// (policy.hasSymbol ? hasSymbol : true) &&
// hasMinLength;
// isValid(policyIsValid);
// return (
// <div className="mb-4 grid grid-cols-2 gap-x-8 gap-y-2">
// <div className="flex flex-row items-center">
// {hasMinLength ? check : cross}
// <span className={desc}>Password length {policy.minLength}</span>
// </div>
// <div className="flex flex-row items-center">
// {hasSymbol ? check : cross}
// <span className={desc}>has Symbol</span>
// </div>
// <div className="flex flex-row items-center">
// {hasNumber ? check : cross}
// <span className={desc}>has Number</span>
// </div>
// <div className="flex flex-row items-center">
// {hasUppercase ? check : cross}
// <span className={desc}>has uppercase</span>
// </div>
// <div className="flex flex-row items-center">
// {hasLowercase ? check : cross}
// <span className={desc}>has lowercase</span>
// </div>
// <div className="flex flex-row items-center">
// {equals ? check : cross}
// <span className={desc}>equals</span>
// </div>
// </div>
// );
// } else {
return null;
// }
}

View File

@@ -0,0 +1,33 @@
'use client';
import React from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import {
useStyledComponentsRegistry,
useStyledJsxRegistry,
} from '#/lib/styling';
export default function RootStyleRegistry({
children,
}: {
children: React.ReactNode;
}) {
const [StyledComponentsRegistry, styledComponentsFlushEffect] =
useStyledComponentsRegistry();
const [StyledJsxRegistry, styledJsxFlushEffect] = useStyledJsxRegistry();
useServerInsertedHTML(() => {
return (
<>
{styledJsxFlushEffect()}
{styledComponentsFlushEffect()}
</>
);
});
return (
<StyledComponentsRegistry>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</StyledComponentsRegistry>
);
}

View File

@@ -0,0 +1,16 @@
import clsx from 'clsx';
export const SkeletonCard = ({ isLoading }: { isLoading?: boolean }) => (
<div
className={clsx('rounded-2xl bg-gray-900/80 p-4', {
'relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:animate-[shimmer_1.5s_infinite] before:bg-gradient-to-r before:from-transparent before:via-white/10 before:to-transparent':
isLoading,
})}
>
<div className="space-y-3">
<div className="h-14 rounded-lg bg-gray-700" />
<div className="h-3 w-11/12 rounded-lg bg-gray-700" />
<div className="h-3 w-8/12 rounded-lg bg-gray-700" />
</div>
</div>
);

35
apps/login/ui/Tab.tsx Normal file
View File

@@ -0,0 +1,35 @@
'use client';
import type { Item } from '#/ui/TabGroup';
import clsx from 'clsx';
import Link from 'next/link';
import { useSelectedLayoutSegment } from 'next/navigation';
export const Tab = ({
path,
item: { slug, text },
}: {
path: string;
item: Item;
}) => {
const segment = useSelectedLayoutSegment();
const href = slug ? path + '/' + slug : path;
const isActive =
// Example home pages e.g. `/layouts`
(!slug && segment === null) ||
// Nested pages e.g. `/layouts/electronics`
segment === slug;
return (
<Link
href={href}
className={clsx('mt-2 mr-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-vercel-blue text-white': isActive,
})}
>
{text}
</Link>
);
};

View File

@@ -0,0 +1,16 @@
import { Tab } from '#/ui/Tab';
export type Item = {
text: string;
slug?: string;
};
export const TabGroup = ({ path, items }: { path: string; items: Item[] }) => {
return (
<div className="-mt-2 flex flex-wrap items-center">
{items.map((item) => (
<Tab key={path + item.slug} item={item} path={path} />
))}
</div>
);
};

View File

@@ -0,0 +1,11 @@
export function VercelLogo() {
return (
<svg
viewBox="0 0 4438 1000"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M2223.75 250C2051.25 250 1926.87 362.5 1926.87 531.25C1926.87 700 2066.72 812.5 2239.38 812.5C2343.59 812.5 2435.47 771.25 2492.34 701.719L2372.81 632.656C2341.25 667.188 2293.28 687.344 2239.38 687.344C2164.53 687.344 2100.94 648.281 2077.34 585.781H2515.16C2518.59 568.281 2520.63 550.156 2520.63 531.094C2520.63 362.5 2396.41 250 2223.75 250ZM2076.09 476.562C2095.62 414.219 2149.06 375 2223.75 375C2298.59 375 2352.03 414.219 2371.41 476.562H2076.09ZM2040.78 78.125L1607.81 828.125L1174.69 78.125H1337.03L1607.66 546.875L1878.28 78.125H2040.78ZM577.344 0L1154.69 1000H0L577.344 0ZM3148.75 531.25C3148.75 625 3210 687.5 3305 687.5C3369.38 687.5 3417.66 658.281 3442.5 610.625L3562.5 679.844C3512.81 762.656 3419.69 812.5 3305 812.5C3132.34 812.5 3008.13 700 3008.13 531.25C3008.13 362.5 3132.5 250 3305 250C3419.69 250 3512.66 299.844 3562.5 382.656L3442.5 451.875C3417.66 404.219 3369.38 375 3305 375C3210.16 375 3148.75 437.5 3148.75 531.25ZM4437.5 78.125V796.875H4296.88V78.125H4437.5ZM3906.25 250C3733.75 250 3609.38 362.5 3609.38 531.25C3609.38 700 3749.38 812.5 3921.88 812.5C4026.09 812.5 4117.97 771.25 4174.84 701.719L4055.31 632.656C4023.75 667.188 3975.78 687.344 3921.88 687.344C3847.03 687.344 3783.44 648.281 3759.84 585.781H4197.66C4201.09 568.281 4203.12 550.156 4203.12 531.094C4203.12 362.5 4078.91 250 3906.25 250ZM3758.59 476.562C3778.13 414.219 3831.41 375 3906.25 375C3981.09 375 4034.53 414.219 4053.91 476.562H3758.59ZM2961.25 265.625V417.031C2945.63 412.5 2929.06 409.375 2911.25 409.375C2820.47 409.375 2755 471.875 2755 565.625V796.875H2614.38V265.625H2755V409.375C2755 330 2847.34 265.625 2961.25 265.625Z" />
</svg>
);
}

View File

@@ -0,0 +1,15 @@
import { ZitadelLogoDark } from './ZitadelLogoDark';
import { ZitadelLogoLight } from './ZitadelLogoLight';
export function ZitadelLogo() {
return (
<>
<div className="hidden dark:flex">
<ZitadelLogoLight />
</div>
<div className="flex dark:hidden">
<ZitadelLogoDark />
</div>
</>
);
}

View File

@@ -0,0 +1,210 @@
import { FC } from 'react';
export const ZitadelLogoDark: FC = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fillRule="evenodd"
strokeLinejoin="round"
strokeMiterlimit="2"
height="100%"
width="100%"
clipRule="evenodd"
viewBox="0 0 467 467"
>
<g transform="matrix(.56485 0 0 .65932 -1282.85 0)">
<path
fill="none"
d="M2271.15 0H3097.9230000000002V708.241H2271.15z"
></path>
<path
fill="#fff"
d="M1493.5 1056.38V1037h3v24.62l-70.48-41.24 70.48-40.988V1004h-3v-19.392l-61.52 35.782 61.52 35.99z"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5923.46 -2258.26)"
></path>
<path
fill="url(#_Linear1)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.04293 -.28297 .16022 -.07582 12884.5 137.392)"
></path>
<path
fill="url(#_Linear2)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(.16022 .07582 -.04293 .28297 12878.9 10.875)"
></path>
<path
fill="url(#_Linear3)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.11729 .20715 -.11729 -.20715 12943.8 65.7)"
></path>
<path
fill="url(#_Linear4)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.16022 -.07582 .04293 -.28297 12917.4 132.195)"
></path>
<path
fill="url(#_Linear5)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.11729 .20715 .11729 .20715 12897.8 5.875)"
></path>
<path
fill="url(#_Linear6)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.04293 -.28297 -.16022 .07582 12936.8 97.644)"
></path>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5928.43 -2257.12)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5884.5 -2116.69)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5855.22 -2023.06)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -6234.47 -2112.14)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5957.71 -2350.75)"
></circle>
<path
fill="#fff"
d="M1499.26 757.787s-1.89-1.298-2.26-2.587c-.29-1.018-.43-4.538-.46-5.2-.13-2.697 2.67-4.356 2.67-4.356l-9.2.191s3.14-.122 3.45 4.165c.05.661-.23 3.476-.46 5.2-.09 1.247-1.8 2.468-1.8 2.468l8.06.119z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5477.99 -831.33)"
></path>
<path
fill="none"
d="M1495 760v-16"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5404.79 -597.271)"
></path>
<g>
<path
fill="#fff"
d="M1498.27 757.077s-1.56-.617-1.62-2.277c0-1.142-.01-1.519 0-2.784-.03-.682-.06-1.408 0-2.067.13-3.113 1.85-3.793 1.85-3.793l-7.04-.225s1.91.788 2.19 3.899c.06.659.04 1.698 0 2.379a88.86 88.86 0 000 2.309c.03 1.816-1.07 2.309-1.07 2.309l5.69.25z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5404.79 -597.271)"
></path>
</g>
<g>
<path
fill="none"
d="M1496.17 759.473l59.37-39.459"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5770.62 -677.495)"
></path>
</g>
<g>
<path
fill="#fff"
d="M1500.86 762.056s-1-1.656 2.23-4.6c1.82-1.659 4.24-3.305 6.89-5.201 4.84-3.465 10.7-7.315 16.54-11.206 4.93-3.283 9.86-6.57 14.3-9.369 3.7-2.331 7.03-4.384 9.72-5.88.53-.294 1.06-.471 1.51-.771 2.68-1.772 4.8-.061 4.8-.061l-4.62-8.686s-.24 3.172-2.23 4.715c-.43.336-.85.744-1.33 1.123-2.47 1.933-5.68 4.224-9.28 6.747-4.33 3.031-9.26 6.299-14.2 9.571-5.84 3.876-11.67 7.796-16.7 10.891-2.75 1.694-5.21 3.248-7.36 4.269-3.14 1.488-5.85.019-5.85.019l5.58 8.439z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5770.62 -677.495)"
></path>
</g>
<g>
<path
fill="none"
d="M1496.17 759.473l59.37-39.459"
transform="matrix(4.96737 -1.14029 -1.16463 -3.72366 -3997 4993.28)"
></path>
</g>
<g>
<path
fill="#fff"
d="M1496.1 754.362s1.1 1.245 5.03-.764c2.12-1.089 4.61-2.575 7.36-4.269 5.03-3.095 10.86-7.015 16.7-10.891 4.94-3.272 9.75-6.606 14.08-9.636 3.6-2.523 10.09-6.743 10.54-7.052 2.94-2.02 2.37-3.554 2.37-3.554l3.1 5.956s-1.51-1.247-3.91.529c-.44.325-6.85 4.668-10.55 6.999-4.44 2.799-9.37 6.086-14.3 9.369-5.84 3.891-11.7 7.741-16.54 11.206-2.65 1.896-5.09 3.516-6.89 5.201-3.62 3.385-1.83 5.827-1.83 5.827l-5.16-8.921z"
transform="matrix(4.96737 -1.14029 -1.16463 -3.72366 -3997 4993.28)"
></path>
</g>
</g>
<defs>
<linearGradient
id="_Linear1"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235) rotate(-75 -.938 .578)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear2"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(160.7235) rotate(-15 4.962 -1.993)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear3"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="rotate(-135 173.811 54.311) scale(160.724)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear4"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235) rotate(-15 -4.103 4.77)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear5"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.724 160.724) rotate(45 -1.58 -1.166)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear6"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235 160.7235) rotate(-75 .592 1.434)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
</defs>
</svg>
);

View File

@@ -0,0 +1,210 @@
import { FC } from 'react';
export const ZitadelLogoLight: FC = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fillRule="evenodd"
strokeLinejoin="round"
strokeMiterlimit="2"
height="100%"
width="100%"
clipRule="evenodd"
viewBox="0 0 467 467"
>
<g transform="matrix(.56485 0 0 .65932 -1282.85 0)">
<path
fill="none"
d="M2271.15 0H3097.9230000000002V708.241H2271.15z"
></path>
<path
fill="#fff"
d="M1493.5 1056.38V1037h3v24.62l-70.48-41.24 70.48-40.988V1004h-3v-19.392l-61.52 35.782 61.52 35.99z"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5923.46 -2258.26)"
></path>
<path
fill="url(#_Linear1)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.04293 -.28297 .16022 -.07582 12884.5 137.392)"
></path>
<path
fill="url(#_Linear2)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(.16022 .07582 -.04293 .28297 12878.9 10.875)"
></path>
<path
fill="url(#_Linear3)"
d="M212.517 110h-12.125L190 92l-10.392 18h-12.125L190 71l22.517 39z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.11729 .20715 -.11729 -.20715 12943.8 65.7)"
></path>
<path
fill="url(#_Linear4)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.16022 -.07582 .04293 -.28297 12917.4 132.195)"
></path>
<path
fill="url(#_Linear5)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.11729 .20715 .11729 .20715 12897.8 5.875)"
></path>
<path
fill="url(#_Linear6)"
d="M139.622 117L149 142h-18.756l9.378-25z"
transform="matrix(31.0036 0 0 15.0393 -397275 -666.457) matrix(-.04293 -.28297 -.16022 .07582 12936.8 97.644)"
></path>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5928.43 -2257.12)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5884.5 -2116.69)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5855.22 -2023.06)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -6234.47 -2112.14)"
></circle>
<circle
cx="1496"
cy="1004"
r="7"
fill="#fff"
transform="matrix(4.96737 -1.14029 1.331 4.25561 -5957.71 -2350.75)"
></circle>
<path
fill="#fff"
d="M1499.26 757.787s-1.89-1.298-2.26-2.587c-.29-1.018-.43-4.538-.46-5.2-.13-2.697 2.67-4.356 2.67-4.356l-9.2.191s3.14-.122 3.45 4.165c.05.661-.23 3.476-.46 5.2-.09 1.247-1.8 2.468-1.8 2.468l8.06.119z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5477.99 -831.33)"
></path>
<path
fill="none"
d="M1495 760v-16"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5404.79 -597.271)"
></path>
<g>
<path
fill="#fff"
d="M1498.27 757.077s-1.56-.617-1.62-2.277c0-1.142-.01-1.519 0-2.784-.03-.682-.06-1.408 0-2.067.13-3.113 1.85-3.793 1.85-3.793l-7.04-.225s1.91.788 2.19 3.899c.06.659.04 1.698 0 2.379a88.86 88.86 0 000 2.309c.03 1.816-1.07 2.309-1.07 2.309l5.69.25z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5404.79 -597.271)"
></path>
</g>
<g>
<path
fill="none"
d="M1496.17 759.473l59.37-39.459"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5770.62 -677.495)"
></path>
</g>
<g>
<path
fill="#fff"
d="M1500.86 762.056s-1-1.656 2.23-4.6c1.82-1.659 4.24-3.305 6.89-5.201 4.84-3.465 10.7-7.315 16.54-11.206 4.93-3.283 9.86-6.57 14.3-9.369 3.7-2.331 7.03-4.384 9.72-5.88.53-.294 1.06-.471 1.51-.771 2.68-1.772 4.8-.061 4.8-.061l-4.62-8.686s-.24 3.172-2.23 4.715c-.43.336-.85.744-1.33 1.123-2.47 1.933-5.68 4.224-9.28 6.747-4.33 3.031-9.26 6.299-14.2 9.571-5.84 3.876-11.67 7.796-16.7 10.891-2.75 1.694-5.21 3.248-7.36 4.269-3.14 1.488-5.85.019-5.85.019l5.58 8.439z"
transform="matrix(4.96737 -1.14029 1.16463 3.72366 -5770.62 -677.495)"
></path>
</g>
<g>
<path
fill="none"
d="M1496.17 759.473l59.37-39.459"
transform="matrix(4.96737 -1.14029 -1.16463 -3.72366 -3997 4993.28)"
></path>
</g>
<g>
<path
fill="#fff"
d="M1496.1 754.362s1.1 1.245 5.03-.764c2.12-1.089 4.61-2.575 7.36-4.269 5.03-3.095 10.86-7.015 16.7-10.891 4.94-3.272 9.75-6.606 14.08-9.636 3.6-2.523 10.09-6.743 10.54-7.052 2.94-2.02 2.37-3.554 2.37-3.554l3.1 5.956s-1.51-1.247-3.91.529c-.44.325-6.85 4.668-10.55 6.999-4.44 2.799-9.37 6.086-14.3 9.369-5.84 3.891-11.7 7.741-16.54 11.206-2.65 1.896-5.09 3.516-6.89 5.201-3.62 3.385-1.83 5.827-1.83 5.827l-5.16-8.921z"
transform="matrix(4.96737 -1.14029 -1.16463 -3.72366 -3997 4993.28)"
></path>
</g>
</g>
<defs>
<linearGradient
id="_Linear1"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235) rotate(-75 -.938 .578)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear2"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(160.7235) rotate(-15 4.962 -1.993)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear3"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="rotate(-135 173.811 54.311) scale(160.724)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear4"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235) rotate(-15 -4.103 4.77)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear5"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.724 160.724) rotate(45 -1.58 -1.166)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
<linearGradient
id="_Linear6"
x1="0"
x2="1"
y1="0"
y2="0"
gradientTransform="scale(-160.7235 160.7235) rotate(-75 .592 1.434)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0" stopColor="#FF8F00"></stop>
<stop offset="1" stopColor="#FE00FF"></stop>
</linearGradient>
</defs>
</svg>
);