-
Notifications
You must be signed in to change notification settings - Fork 573
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
75ef605
commit a90f3e2
Showing
9 changed files
with
532 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
packages/oauth-provider/src/assets/app/components/help-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { HTMLAttributes } from 'react' | ||
import { LinkDefinition } from '../backend-data' | ||
import { clsx } from '../lib/clsx' | ||
|
||
export type HelpCardProps = { | ||
links?: readonly LinkDefinition[] | ||
} | ||
|
||
export function HelpCard({ | ||
links, | ||
|
||
className, | ||
...attrs | ||
}: HelpCardProps & | ||
Omit< | ||
HTMLAttributes<HTMLParagraphElement>, | ||
keyof HelpCardProps | 'children' | ||
>) { | ||
const helpLink = links?.find((l) => l.rel === 'help') | ||
|
||
if (!helpLink) return null | ||
|
||
return ( | ||
<p | ||
className={clsx( | ||
'text-sm rounded-md bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-400 p-3', | ||
className, | ||
)} | ||
{...attrs} | ||
> | ||
Having trouble?{' '} | ||
<a | ||
href={helpLink.href} | ||
rel={helpLink.rel} | ||
target="_blank" | ||
className="text-primary" | ||
> | ||
Contact {helpLink.title} | ||
</a> | ||
</p> | ||
) | ||
} |
208 changes: 208 additions & 0 deletions
208
packages/oauth-provider/src/assets/app/components/sign-up-account-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
import { | ||
FormHTMLAttributes, | ||
ReactNode, | ||
SyntheticEvent, | ||
useCallback, | ||
useState, | ||
} from 'react' | ||
|
||
import { clsx } from '../lib/clsx' | ||
import { ErrorCard } from './error-card' | ||
|
||
export type SignUpAccountFormOutput = { | ||
username: string | ||
password: string | ||
} | ||
|
||
export type SignUpAccountFormProps = { | ||
onSubmit: (credentials: SignUpAccountFormOutput) => void | PromiseLike<void> | ||
submitLabel?: ReactNode | ||
submitAria?: string | ||
|
||
onCancel?: () => void | ||
cancelLabel?: ReactNode | ||
cancelAria?: string | ||
|
||
username?: string | ||
usernamePlaceholder?: string | ||
usernameLabel?: string | ||
usernameAria?: string | ||
usernamePattern?: string | ||
usernameTitle?: string | ||
|
||
passwordPlaceholder?: string | ||
passwordLabel?: string | ||
passwordAria?: string | ||
passwordPattern?: string | ||
passwordTitle?: string | ||
} | ||
|
||
export function SignUpAccountForm({ | ||
onSubmit, | ||
submitAria = 'Next', | ||
submitLabel = submitAria, | ||
|
||
onCancel = undefined, | ||
cancelAria = 'Cancel', | ||
cancelLabel = cancelAria, | ||
|
||
username: defaultUsername = '', | ||
usernameLabel = 'Username', | ||
usernameAria = usernameLabel, | ||
usernamePlaceholder = usernameLabel, | ||
usernamePattern, | ||
usernameTitle, | ||
|
||
passwordLabel = 'Password', | ||
passwordAria = passwordLabel, | ||
passwordPlaceholder = passwordLabel, | ||
passwordPattern, | ||
passwordTitle, | ||
|
||
className, | ||
children, | ||
...attrs | ||
}: SignUpAccountFormProps & | ||
Omit<FormHTMLAttributes<HTMLFormElement>, keyof SignUpAccountFormProps>) { | ||
const [loading, setLoading] = useState(false) | ||
const [errorMessage, setErrorMessage] = useState<string | null>(null) | ||
|
||
const doSubmit = useCallback( | ||
async ( | ||
event: SyntheticEvent< | ||
HTMLFormElement & { | ||
username: HTMLInputElement | ||
password: HTMLInputElement | ||
}, | ||
SubmitEvent | ||
>, | ||
) => { | ||
event.preventDefault() | ||
|
||
const credentials = { | ||
username: event.currentTarget.username.value, | ||
password: event.currentTarget.password.value, | ||
} | ||
|
||
setLoading(true) | ||
setErrorMessage(null) | ||
try { | ||
await onSubmit(credentials) | ||
} catch (err) { | ||
setErrorMessage(parseErrorMessage(err)) | ||
} finally { | ||
setLoading(false) | ||
} | ||
}, | ||
[onSubmit, setErrorMessage, setLoading], | ||
) | ||
|
||
return ( | ||
<form | ||
{...attrs} | ||
className={clsx('flex flex-col', className)} | ||
onSubmit={doSubmit} | ||
> | ||
<fieldset disabled={loading}> | ||
<label className="text-sm font-medium" htmlFor="username"> | ||
{usernameLabel} | ||
</label> | ||
|
||
<div | ||
id="username" | ||
className="mb-4 relative p-1 flex flex-wrap items-center justify-stretch rounded-md border border-solid border-slate-200 dark:border-slate-700 text-neutral-700 dark:text-neutral-100" | ||
> | ||
<span className="w-8 text-center text-base leading-[1.6]">@</span> | ||
<input | ||
name="username" | ||
type="text" | ||
onChange={() => setErrorMessage(null)} | ||
className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500" | ||
placeholder={usernamePlaceholder} | ||
aria-label={usernameAria} | ||
autoCapitalize="none" | ||
autoCorrect="off" | ||
autoComplete="username" | ||
spellCheck="false" | ||
dir="auto" | ||
enterKeyHint="next" | ||
required | ||
defaultValue={defaultUsername} | ||
pattern={usernamePattern} | ||
title={usernameTitle} | ||
/> | ||
</div> | ||
|
||
<label className="text-sm font-medium" htmlFor="password"> | ||
{passwordLabel} | ||
</label> | ||
|
||
<div | ||
id="password" | ||
className="mb-4 relative p-1 flex flex-wrap items-center justify-stretch rounded-md border border-solid border-slate-200 dark:border-slate-700 text-neutral-700 dark:text-neutral-100" | ||
> | ||
<span className="w-8 text-center text-2xl leading-[1.6]">*</span> | ||
<input | ||
name="password" | ||
type="password" | ||
onChange={() => setErrorMessage(null)} | ||
className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100" | ||
placeholder={passwordPlaceholder} | ||
aria-label={passwordAria} | ||
autoCapitalize="none" | ||
autoCorrect="off" | ||
autoComplete="new-password" | ||
dir="auto" | ||
enterKeyHint="done" | ||
spellCheck="false" | ||
required | ||
pattern={passwordPattern} | ||
title={passwordTitle} | ||
/> | ||
</div> | ||
</fieldset> | ||
|
||
{children && <div className="mt-4">{children}</div>} | ||
|
||
{errorMessage && <ErrorCard className="mt-2" message={errorMessage} />} | ||
|
||
<div className="flex-auto"></div> | ||
|
||
<div className="p-4 flex flex-wrap items-center justify-start"> | ||
<button | ||
className="py-2 bg-transparent text-primary rounded-md font-semibold order-last" | ||
type="submit" | ||
role="Button" | ||
aria-label={submitAria} | ||
disabled={loading} | ||
> | ||
{submitLabel} | ||
</button> | ||
|
||
{onCancel && ( | ||
<button | ||
className="py-2 bg-transparent text-primary rounded-md font-light" | ||
type="button" | ||
role="Button" | ||
aria-label={cancelAria} | ||
onClick={onCancel} | ||
> | ||
{cancelLabel} | ||
</button> | ||
)} | ||
|
||
<div className="flex-auto" /> | ||
</div> | ||
</form> | ||
) | ||
} | ||
|
||
function parseErrorMessage(err: unknown): string { | ||
switch ((err as any)?.message) { | ||
case 'Invalid credentials': | ||
return 'Invalid username or password' | ||
default: | ||
console.error(err) | ||
return 'An unknown error occurred' | ||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
packages/oauth-provider/src/assets/app/components/sign-up-disclaimer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { HTMLAttributes } from 'react' | ||
import { LinkDefinition } from '../backend-data' | ||
import { clsx } from '../lib/clsx' | ||
|
||
export type SignUpDisclaimerProps = { | ||
links?: readonly LinkDefinition[] | ||
} | ||
|
||
export function SignUpDisclaimer({ | ||
links, | ||
|
||
className, | ||
...attrs | ||
}: SignUpDisclaimerProps & | ||
Omit< | ||
HTMLAttributes<HTMLParagraphElement>, | ||
keyof SignUpDisclaimerProps | 'children' | ||
>) { | ||
const relevantLinks = links?.filter( | ||
(l) => l.rel === 'privacy-policy' || l.rel === 'terms-of-service', | ||
) | ||
|
||
return ( | ||
<p className={clsx('text-sm text-slate-500', className)} {...attrs}> | ||
By creating an account you agree to the{' '} | ||
{relevantLinks && relevantLinks.length | ||
? relevantLinks.map((l, i, a) => ( | ||
<span key={i}> | ||
{i > 0 && (i < a.length - 1 ? ', ' : ' and ')} | ||
<a | ||
href={l.href} | ||
rel={l.rel} | ||
target="_blank" | ||
className="text-primary underline" | ||
> | ||
{l.title} | ||
</a> | ||
</span> | ||
)) | ||
: 'Terms of Service and Privacy Policy'} | ||
. | ||
</p> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.