@atproto/oauth-provider-ui 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authorization-page-Dhx8lvtZ.js +3 -0
- package/dist/authorization-page-Dhx8lvtZ.js.map +1 -0
- package/dist/bundle-manifest.json +630 -0
- package/dist/error-page-DC6Vc-cv.js +2 -0
- package/dist/error-page-DC6Vc-cv.js.map +1 -0
- package/dist/error-view-CRGNTAn2.css +1 -0
- package/dist/error-view-MVy7C9l0.js +59 -0
- package/dist/error-view-MVy7C9l0.js.map +1 -0
- package/dist/index-CHPoD7Rp.js +20 -0
- package/dist/index-CHPoD7Rp.js.map +1 -0
- package/dist/messages-B0mgsxS-.js +2 -0
- package/dist/messages-B0mgsxS-.js.map +1 -0
- package/dist/messages-B5g8Fkio.js +2 -0
- package/dist/messages-B5g8Fkio.js.map +1 -0
- package/dist/messages-BCMss-Kt.js +2 -0
- package/dist/messages-BCMss-Kt.js.map +1 -0
- package/dist/messages-BGUrKgyK.js +2 -0
- package/dist/messages-BGUrKgyK.js.map +1 -0
- package/dist/messages-BjxAnLDp.js +2 -0
- package/dist/messages-BjxAnLDp.js.map +1 -0
- package/dist/messages-Bjysz3rI.js +2 -0
- package/dist/messages-Bjysz3rI.js.map +1 -0
- package/dist/messages-BvvEr3UX.js +2 -0
- package/dist/messages-BvvEr3UX.js.map +1 -0
- package/dist/messages-Bz6JOhJf.js +2 -0
- package/dist/messages-Bz6JOhJf.js.map +1 -0
- package/dist/messages-BzL3D1EU.js +2 -0
- package/dist/messages-BzL3D1EU.js.map +1 -0
- package/dist/messages-CAvN5UoW.js +2 -0
- package/dist/messages-CAvN5UoW.js.map +1 -0
- package/dist/messages-CEmswT1Q.js +2 -0
- package/dist/messages-CEmswT1Q.js.map +1 -0
- package/dist/messages-CHYqz0q6.js +2 -0
- package/dist/messages-CHYqz0q6.js.map +1 -0
- package/dist/messages-CRmpdijj.js +2 -0
- package/dist/messages-CRmpdijj.js.map +1 -0
- package/dist/messages-Cdb79R6S.js +2 -0
- package/dist/messages-Cdb79R6S.js.map +1 -0
- package/dist/messages-ChkJ_0WT.js +2 -0
- package/dist/messages-ChkJ_0WT.js.map +1 -0
- package/dist/messages-CqiEX6JJ.js +2 -0
- package/dist/messages-CqiEX6JJ.js.map +1 -0
- package/dist/messages-CxkHjJSR.js +2 -0
- package/dist/messages-CxkHjJSR.js.map +1 -0
- package/dist/messages-D0-cWoJ9.js +2 -0
- package/dist/messages-D0-cWoJ9.js.map +1 -0
- package/dist/messages-D2MnAxYY.js +2 -0
- package/dist/messages-D2MnAxYY.js.map +1 -0
- package/dist/messages-D5TZVsui.js +2 -0
- package/dist/messages-D5TZVsui.js.map +1 -0
- package/dist/messages-DBdV4-iw.js +2 -0
- package/dist/messages-DBdV4-iw.js.map +1 -0
- package/dist/messages-DEK3zybC.js +2 -0
- package/dist/messages-DEK3zybC.js.map +1 -0
- package/dist/messages-DGSM5jkd.js +2 -0
- package/dist/messages-DGSM5jkd.js.map +1 -0
- package/dist/messages-DJgAnSTQ.js +2 -0
- package/dist/messages-DJgAnSTQ.js.map +1 -0
- package/dist/messages-DK7O7sb_.js +2 -0
- package/dist/messages-DK7O7sb_.js.map +1 -0
- package/dist/messages-DRp7qc3j.js +2 -0
- package/dist/messages-DRp7qc3j.js.map +1 -0
- package/dist/messages-DT6xRw0m.js +2 -0
- package/dist/messages-DT6xRw0m.js.map +1 -0
- package/dist/messages-LnzLtU0L.js +2 -0
- package/dist/messages-LnzLtU0L.js.map +1 -0
- package/dist/messages-_Nk2qNGw.js +2 -0
- package/dist/messages-_Nk2qNGw.js.map +1 -0
- package/dist/messages-eHH6nZyF.js +2 -0
- package/dist/messages-eHH6nZyF.js.map +1 -0
- package/dist/messages-iNw8zY2C.js +2 -0
- package/dist/messages-iNw8zY2C.js.map +1 -0
- package/dist/messages-ipc0L8yF.js +2 -0
- package/dist/messages-ipc0L8yF.js.map +1 -0
- package/dist/messages-j7LsWm2F.js +2 -0
- package/dist/messages-j7LsWm2F.js.map +1 -0
- package/dist/messages-mgE_5UEw.js +2 -0
- package/dist/messages-mgE_5UEw.js.map +1 -0
- package/dist/messages-oRd-J5--.js +2 -0
- package/dist/messages-oRd-J5--.js.map +1 -0
- package/package.json +10 -8
- package/.linguirc +0 -57
- package/CHANGELOG.md +0 -17
- package/CONTRIBUTING.md +0 -6
- package/authorization-page.html +0 -186
- package/error-page.html +0 -118
- package/index.html +0 -13
- package/src/authorization-page.tsx +0 -49
- package/src/components/forms/button-toggle-visibility.tsx +0 -43
- package/src/components/forms/button.tsx +0 -60
- package/src/components/forms/fieldset.tsx +0 -55
- package/src/components/forms/form-card-async.tsx +0 -103
- package/src/components/forms/form-card.tsx +0 -49
- package/src/components/forms/input-checkbox.tsx +0 -78
- package/src/components/forms/input-container.tsx +0 -107
- package/src/components/forms/input-email-address.tsx +0 -65
- package/src/components/forms/input-new-password.tsx +0 -62
- package/src/components/forms/input-password.tsx +0 -87
- package/src/components/forms/input-text.tsx +0 -82
- package/src/components/forms/input-token.tsx +0 -94
- package/src/components/forms/wizard-card.tsx +0 -116
- package/src/components/layouts/layout-title-page.tsx +0 -78
- package/src/components/layouts/layout-welcome.tsx +0 -78
- package/src/components/utils/account-identifier.tsx +0 -23
- package/src/components/utils/account-image.tsx +0 -33
- package/src/components/utils/admonition.tsx +0 -52
- package/src/components/utils/client-name.tsx +0 -71
- package/src/components/utils/error-card.tsx +0 -93
- package/src/components/utils/error-message.tsx +0 -88
- package/src/components/utils/help-card.tsx +0 -46
- package/src/components/utils/icons.tsx +0 -88
- package/src/components/utils/link-anchor.tsx +0 -28
- package/src/components/utils/link-title.tsx +0 -26
- package/src/components/utils/multi-lang-string.tsx +0 -62
- package/src/components/utils/password-strength-label.tsx +0 -37
- package/src/components/utils/password-strength-meter.tsx +0 -58
- package/src/components/utils/url-viewer.tsx +0 -73
- package/src/error-page.tsx +0 -23
- package/src/hooks/use-api.ts +0 -202
- package/src/hooks/use-async-action.ts +0 -120
- package/src/hooks/use-bound-dispatch.ts +0 -5
- package/src/hooks/use-browser-color-scheme.ts +0 -31
- package/src/hooks/use-random-string.ts +0 -37
- package/src/hooks/use-stepper.ts +0 -87
- package/src/lib/api.ts +0 -225
- package/src/lib/cookies.ts +0 -17
- package/src/lib/json-client.ts +0 -141
- package/src/lib/password.ts +0 -98
- package/src/lib/ref.ts +0 -17
- package/src/lib/util.ts +0 -14
- package/src/locales/an/messages.po +0 -494
- package/src/locales/ast/messages.po +0 -494
- package/src/locales/ca/messages.po +0 -494
- package/src/locales/da/messages.po +0 -494
- package/src/locales/de/messages.po +0 -494
- package/src/locales/el/messages.po +0 -494
- package/src/locales/en/messages.po +0 -494
- package/src/locales/en-GB/messages.po +0 -494
- package/src/locales/es/messages.po +0 -494
- package/src/locales/eu/messages.po +0 -494
- package/src/locales/fi/messages.po +0 -494
- package/src/locales/fr/messages.po +0 -494
- package/src/locales/ga/messages.po +0 -494
- package/src/locales/gl/messages.po +0 -494
- package/src/locales/hi/messages.po +0 -494
- package/src/locales/hu/messages.po +0 -494
- package/src/locales/ia/messages.po +0 -494
- package/src/locales/id/messages.po +0 -494
- package/src/locales/it/messages.po +0 -494
- package/src/locales/ja/messages.po +0 -494
- package/src/locales/km/messages.po +0 -494
- package/src/locales/ko/messages.po +0 -494
- package/src/locales/load.ts +0 -8
- package/src/locales/locale-provider.tsx +0 -108
- package/src/locales/locale-selector.tsx +0 -57
- package/src/locales/locales.ts +0 -183
- package/src/locales/ne/messages.po +0 -494
- package/src/locales/nl/messages.po +0 -494
- package/src/locales/pl/messages.po +0 -494
- package/src/locales/pt-BR/messages.po +0 -494
- package/src/locales/ro/messages.po +0 -494
- package/src/locales/ru/messages.po +0 -494
- package/src/locales/sv/messages.po +0 -494
- package/src/locales/th/messages.po +0 -494
- package/src/locales/tr/messages.po +0 -494
- package/src/locales/uk/messages.po +0 -494
- package/src/locales/vi/messages.po +0 -494
- package/src/locales/zh-CN/messages.po +0 -494
- package/src/locales/zh-HK/messages.po +0 -494
- package/src/locales/zh-TW/messages.po +0 -494
- package/src/style.css +0 -219
- package/src/views/authorize/accept/accept-form.tsx +0 -155
- package/src/views/authorize/accept/accept-view.tsx +0 -70
- package/src/views/authorize/authorize-view.tsx +0 -186
- package/src/views/authorize/reset-password/reset-password-confirm-form.tsx +0 -88
- package/src/views/authorize/reset-password/reset-password-request-form.tsx +0 -80
- package/src/views/authorize/reset-password/reset-password-view.tsx +0 -127
- package/src/views/authorize/sign-in/sign-in-form.tsx +0 -240
- package/src/views/authorize/sign-in/sign-in-picker.tsx +0 -116
- package/src/views/authorize/sign-in/sign-in-view.tsx +0 -145
- package/src/views/authorize/sign-up/sign-up-account-form.tsx +0 -142
- package/src/views/authorize/sign-up/sign-up-disclaimer.tsx +0 -51
- package/src/views/authorize/sign-up/sign-up-handle-form.tsx +0 -287
- package/src/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +0 -108
- package/src/views/authorize/sign-up/sign-up-view.tsx +0 -158
- package/src/views/authorize/welcome/welcome-view.tsx +0 -56
- package/src/views/error/error-view.tsx +0 -31
- package/tsconfig.json +0 -7
- package/tsconfig.src.json +0 -13
- package/tsconfig.src.tsbuildinfo +0 -1
- package/tsconfig.tools.json +0 -8
- package/tsconfig.tools.tsbuildinfo +0 -1
- package/vite.config.mjs +0 -47
- /package/{src/hydration-data.d.ts → hydration-data.d.ts} +0 -0
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Trans } from '@lingui/react/macro'
|
|
2
|
-
import type { LinkDefinition } from '@atproto/oauth-provider-api'
|
|
3
|
-
import { MultiLangString } from './multi-lang-string.tsx'
|
|
4
|
-
|
|
5
|
-
export type LinkNameProps = {
|
|
6
|
-
link: LinkDefinition
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function LinkTitle({ link }: LinkNameProps) {
|
|
10
|
-
return (
|
|
11
|
-
<MultiLangString
|
|
12
|
-
value={link.title}
|
|
13
|
-
fallback={
|
|
14
|
-
link.rel === 'canonical' ? (
|
|
15
|
-
<Trans>Home</Trans>
|
|
16
|
-
) : link.rel === 'privacy-policy' ? (
|
|
17
|
-
<Trans>Privacy Policy</Trans>
|
|
18
|
-
) : link.rel === 'terms-of-service' ? (
|
|
19
|
-
<Trans>Terms of Service</Trans>
|
|
20
|
-
) : link.rel === 'help' ? (
|
|
21
|
-
<Trans>Support</Trans>
|
|
22
|
-
) : undefined
|
|
23
|
-
}
|
|
24
|
-
/>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { useLingui } from '@lingui/react/macro'
|
|
2
|
-
import { ReactNode, useMemo } from 'react'
|
|
3
|
-
import type { LocalizedString } from '@atproto/oauth-provider-api'
|
|
4
|
-
|
|
5
|
-
export type MultiLangStringProps = {
|
|
6
|
-
value: LocalizedString
|
|
7
|
-
fallback?: ReactNode
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function MultiLangString({
|
|
11
|
-
value,
|
|
12
|
-
fallback,
|
|
13
|
-
}: MultiLangStringProps): ReactNode {
|
|
14
|
-
const matchingString = useMatchingString(value)
|
|
15
|
-
return (
|
|
16
|
-
matchingString ?? fallback ?? (typeof value === 'string' ? value : value.en)
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function useMatchingString(value: LocalizedString) {
|
|
21
|
-
const { i18n } = useLingui()
|
|
22
|
-
return useMemo(
|
|
23
|
-
() => findMatchingString(value, i18n.locale),
|
|
24
|
-
[value, i18n.locale],
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Only returns a string if it matches the desired locale.
|
|
30
|
-
*/
|
|
31
|
-
function findMatchingString(
|
|
32
|
-
value: LocalizedString,
|
|
33
|
-
locale: string,
|
|
34
|
-
): string | undefined {
|
|
35
|
-
switch (typeof value) {
|
|
36
|
-
case 'string':
|
|
37
|
-
// By convention, string values are in english
|
|
38
|
-
if (locale === 'en' || locale.startsWith('en-')) return value
|
|
39
|
-
break
|
|
40
|
-
|
|
41
|
-
case 'object': {
|
|
42
|
-
// Exact match
|
|
43
|
-
const localeMatch = value[locale]
|
|
44
|
-
if (typeof localeMatch === 'string') return localeMatch
|
|
45
|
-
|
|
46
|
-
// Fallback to language match (e.g. "fr-BE" -> "fr")
|
|
47
|
-
const lang = locale.split('-')[0]
|
|
48
|
-
const langMatch = value[lang]
|
|
49
|
-
if (typeof langMatch === 'string') return langMatch
|
|
50
|
-
|
|
51
|
-
// Fallback to any locale from same language (e.g. "pt-PT" -> "pt-BR")
|
|
52
|
-
for (const k in value) {
|
|
53
|
-
if (k.startsWith(`${lang}-`)) {
|
|
54
|
-
const fallbackMatch = value[k]
|
|
55
|
-
if (typeof fallbackMatch === 'string') return fallbackMatch
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return undefined
|
|
62
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Trans, useLingui } from '@lingui/react/macro'
|
|
2
|
-
import { JSX } from 'react'
|
|
3
|
-
import { PasswordStrength, getPasswordStrength } from '../../lib/password.ts'
|
|
4
|
-
import { Override } from '../../lib/util.ts'
|
|
5
|
-
|
|
6
|
-
export type PasswordStrengthLabelProps = Override<
|
|
7
|
-
Omit<JSX.IntrinsicElements['span'], 'children' | 'aria-label'>,
|
|
8
|
-
{
|
|
9
|
-
password: string
|
|
10
|
-
}
|
|
11
|
-
>
|
|
12
|
-
|
|
13
|
-
export function PasswordStrengthLabel({
|
|
14
|
-
password,
|
|
15
|
-
|
|
16
|
-
// span
|
|
17
|
-
...props
|
|
18
|
-
}: PasswordStrengthLabelProps) {
|
|
19
|
-
const { t } = useLingui()
|
|
20
|
-
const strength = getPasswordStrength(password)
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<span {...props} aria-label={t`Password strength`}>
|
|
24
|
-
{strength === PasswordStrength.extra ? (
|
|
25
|
-
<Trans>Extra</Trans>
|
|
26
|
-
) : strength === PasswordStrength.strong ? (
|
|
27
|
-
<Trans>Strong</Trans>
|
|
28
|
-
) : strength === PasswordStrength.moderate ? (
|
|
29
|
-
<Trans>Moderate</Trans>
|
|
30
|
-
) : password ? (
|
|
31
|
-
<Trans>Weak</Trans>
|
|
32
|
-
) : (
|
|
33
|
-
<Trans>Missing</Trans>
|
|
34
|
-
)}
|
|
35
|
-
</span>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { useLingui } from '@lingui/react/macro'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
|
-
import { JSX } from 'react'
|
|
4
|
-
import { PasswordStrength, getPasswordStrength } from '../../lib/password.ts'
|
|
5
|
-
import { Override } from '../../lib/util.ts'
|
|
6
|
-
|
|
7
|
-
export type PasswordStrengthMeterProps = Override<
|
|
8
|
-
Omit<
|
|
9
|
-
JSX.IntrinsicElements['div'],
|
|
10
|
-
| 'children'
|
|
11
|
-
| 'role'
|
|
12
|
-
| 'aria-label'
|
|
13
|
-
| 'aria-valuemin'
|
|
14
|
-
| 'aria-valuemax'
|
|
15
|
-
| 'aria-valuenow'
|
|
16
|
-
>,
|
|
17
|
-
{
|
|
18
|
-
password: string
|
|
19
|
-
}
|
|
20
|
-
>
|
|
21
|
-
|
|
22
|
-
export function PasswordStrengthMeter({
|
|
23
|
-
password,
|
|
24
|
-
|
|
25
|
-
// div
|
|
26
|
-
className,
|
|
27
|
-
...props
|
|
28
|
-
}: PasswordStrengthMeterProps) {
|
|
29
|
-
const { t } = useLingui()
|
|
30
|
-
const strength = password ? getPasswordStrength(password) : 0
|
|
31
|
-
|
|
32
|
-
const colorBg = 'bg-gray-300 dark:bg-slate-500'
|
|
33
|
-
const color =
|
|
34
|
-
strength === PasswordStrength.extra || strength === PasswordStrength.strong
|
|
35
|
-
? 'bg-success'
|
|
36
|
-
: strength === PasswordStrength.moderate
|
|
37
|
-
? 'bg-warning'
|
|
38
|
-
: 'bg-error'
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div
|
|
42
|
-
{...props}
|
|
43
|
-
className={clsx('flex h-1 w-full space-x-2', className)}
|
|
44
|
-
role="meter"
|
|
45
|
-
aria-label={t`Password strength indicator`}
|
|
46
|
-
aria-valuemin={0}
|
|
47
|
-
aria-valuemax={PasswordStrength.extra}
|
|
48
|
-
aria-valuenow={strength}
|
|
49
|
-
>
|
|
50
|
-
{Array.from({ length: 4 }, (_, i) => (
|
|
51
|
-
<div
|
|
52
|
-
key={i}
|
|
53
|
-
className={`h-1 w-1/4 rounded-sm ${strength > i ? color : colorBg}`}
|
|
54
|
-
/>
|
|
55
|
-
))}
|
|
56
|
-
</div>
|
|
57
|
-
)
|
|
58
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { JSX, useMemo } from 'react'
|
|
2
|
-
import { Override } from '../../lib/util.ts'
|
|
3
|
-
|
|
4
|
-
export type UrlPartRenderingOptions = {
|
|
5
|
-
faded?: boolean
|
|
6
|
-
bold?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type UrlRendererProps = {
|
|
10
|
-
url: string | URL
|
|
11
|
-
proto?: boolean | UrlPartRenderingOptions
|
|
12
|
-
host?: boolean | UrlPartRenderingOptions
|
|
13
|
-
path?: boolean | UrlPartRenderingOptions
|
|
14
|
-
query?: boolean | UrlPartRenderingOptions
|
|
15
|
-
hash?: boolean | UrlPartRenderingOptions
|
|
16
|
-
as?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function UrlViewer<As extends keyof JSX.IntrinsicElements = 'span'>({
|
|
20
|
-
url,
|
|
21
|
-
proto = false,
|
|
22
|
-
host = true,
|
|
23
|
-
path = false,
|
|
24
|
-
query = false,
|
|
25
|
-
hash = false,
|
|
26
|
-
as: As = 'span',
|
|
27
|
-
|
|
28
|
-
// Element
|
|
29
|
-
...props
|
|
30
|
-
}: Override<JSX.IntrinsicElements[As], UrlRendererProps>) {
|
|
31
|
-
const urlObj = useMemo(() => (url instanceof URL ? url : new URL(url)), [url])
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<As {...props}>
|
|
35
|
-
{proto && (
|
|
36
|
-
<UrlPartViewer
|
|
37
|
-
value={`${urlObj.protocol}//`}
|
|
38
|
-
{...(proto === true ? null : proto)}
|
|
39
|
-
/>
|
|
40
|
-
)}
|
|
41
|
-
{host && (
|
|
42
|
-
<UrlPartViewer
|
|
43
|
-
value={urlObj.host}
|
|
44
|
-
{...(host === true ? { faded: false, bold: true } : host)}
|
|
45
|
-
/>
|
|
46
|
-
)}
|
|
47
|
-
{path && (
|
|
48
|
-
<UrlPartViewer
|
|
49
|
-
value={urlObj.pathname}
|
|
50
|
-
{...(path === true ? null : path)}
|
|
51
|
-
/>
|
|
52
|
-
)}
|
|
53
|
-
{query && (
|
|
54
|
-
<UrlPartViewer
|
|
55
|
-
value={urlObj.search}
|
|
56
|
-
{...(query === true ? null : query)}
|
|
57
|
-
/>
|
|
58
|
-
)}
|
|
59
|
-
{hash && (
|
|
60
|
-
<UrlPartViewer value={urlObj.hash} {...(hash === true ? null : hash)} />
|
|
61
|
-
)}
|
|
62
|
-
</As>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function UrlPartViewer({
|
|
67
|
-
value,
|
|
68
|
-
faded = true,
|
|
69
|
-
bold = false,
|
|
70
|
-
}: { value: string } & UrlPartRenderingOptions) {
|
|
71
|
-
const Comp = bold ? 'b' : 'span'
|
|
72
|
-
return <Comp className={faded ? 'opacity-50' : ''}>{value}</Comp>
|
|
73
|
-
}
|
package/src/error-page.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import './style.css'
|
|
2
|
-
|
|
3
|
-
import { StrictMode } from 'react'
|
|
4
|
-
import { createRoot } from 'react-dom/client'
|
|
5
|
-
import type { HydrationData } from './hydration-data.d.ts'
|
|
6
|
-
import { LocaleProvider } from './locales/locale-provider.tsx'
|
|
7
|
-
import { ErrorView } from './views/error/error-view.tsx'
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
//
|
|
11
|
-
__errorData: errorData,
|
|
12
|
-
__customizationData: customizationData,
|
|
13
|
-
} = window as typeof window & HydrationData['error-page']
|
|
14
|
-
|
|
15
|
-
const container = document.getElementById('root')!
|
|
16
|
-
|
|
17
|
-
createRoot(container).render(
|
|
18
|
-
<StrictMode>
|
|
19
|
-
<LocaleProvider>
|
|
20
|
-
<ErrorView error={errorData} customizationData={customizationData} />
|
|
21
|
-
</LocaleProvider>
|
|
22
|
-
</StrictMode>,
|
|
23
|
-
)
|
package/src/hooks/use-api.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { useLingui } from '@lingui/react/macro'
|
|
2
|
-
import { useCallback, useState } from 'react'
|
|
3
|
-
import { useErrorBoundary } from 'react-error-boundary'
|
|
4
|
-
import type {
|
|
5
|
-
Account,
|
|
6
|
-
ConfirmResetPasswordInput,
|
|
7
|
-
InitiatePasswordResetInput,
|
|
8
|
-
Session,
|
|
9
|
-
SignInInput,
|
|
10
|
-
SignUpInput,
|
|
11
|
-
VerifyHandleAvailabilityInput,
|
|
12
|
-
} from '@atproto/oauth-provider-api'
|
|
13
|
-
import { Api, UnknownRequestUriError } from '../lib/api.ts'
|
|
14
|
-
import { upsert } from '../lib/util.ts'
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Any function wrapped with this helper will automatically show the error
|
|
18
|
-
* boundary when an `UnknownRequestUriError` is thrown. This typically happens
|
|
19
|
-
* in development, or if the user left its browser session open for a (very)
|
|
20
|
-
* long time.
|
|
21
|
-
*
|
|
22
|
-
* @note Requires an error boundary to be present in the component tree.
|
|
23
|
-
*/
|
|
24
|
-
function useSafeCallback<F extends (...a: any) => any>(fn: F, deps: unknown[]) {
|
|
25
|
-
const { showBoundary } = useErrorBoundary<UnknownRequestUriError>()
|
|
26
|
-
|
|
27
|
-
return useCallback(
|
|
28
|
-
async (...args: Parameters<F>): Promise<Awaited<ReturnType<F>>> => {
|
|
29
|
-
try {
|
|
30
|
-
return await fn(...args)
|
|
31
|
-
} catch (error) {
|
|
32
|
-
if (error instanceof UnknownRequestUriError) showBoundary(error)
|
|
33
|
-
throw error
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
deps.concat(showBoundary),
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type SessionWithToken = Session & {
|
|
41
|
-
ephemeralToken?: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function useApi({
|
|
45
|
-
sessions: sessionsInit = [],
|
|
46
|
-
onRedirected,
|
|
47
|
-
}: {
|
|
48
|
-
sessions?: readonly Session[]
|
|
49
|
-
onRedirected?: () => void
|
|
50
|
-
}) {
|
|
51
|
-
const [api] = useState(() => new Api())
|
|
52
|
-
const [sessions, setSessions] =
|
|
53
|
-
useState<readonly SessionWithToken[]>(sessionsInit)
|
|
54
|
-
|
|
55
|
-
const { i18n } = useLingui()
|
|
56
|
-
const { locale } = i18n
|
|
57
|
-
|
|
58
|
-
const selectSub = useCallback(
|
|
59
|
-
(sub: string | null) => {
|
|
60
|
-
setSessions((sessions) =>
|
|
61
|
-
sub === (sessions.find((s) => s.selected)?.account.sub || null)
|
|
62
|
-
? sessions
|
|
63
|
-
: sessions.map((s) => ({ ...s, selected: s.account.sub === sub })),
|
|
64
|
-
)
|
|
65
|
-
},
|
|
66
|
-
[setSessions],
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
const upsertSession = useCallback(
|
|
70
|
-
({
|
|
71
|
-
account,
|
|
72
|
-
ephemeralToken,
|
|
73
|
-
// The server will tell us if the user needs to consent to the
|
|
74
|
-
// authorization. Defaults to true in case of sign-ups
|
|
75
|
-
consentRequired = true,
|
|
76
|
-
// When a new session is inserted, assume that the user intends to use
|
|
77
|
-
// it, and therefore, it is selected by default.
|
|
78
|
-
selected = true,
|
|
79
|
-
// When a new session is inserted, it is assumed that the user just
|
|
80
|
-
// created the session, and therefore, login is not required.
|
|
81
|
-
loginRequired = false,
|
|
82
|
-
}: { account: Account } & Partial<SessionWithToken>) => {
|
|
83
|
-
const session: SessionWithToken = {
|
|
84
|
-
account,
|
|
85
|
-
ephemeralToken,
|
|
86
|
-
selected,
|
|
87
|
-
loginRequired,
|
|
88
|
-
consentRequired,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setSessions((sessions) =>
|
|
92
|
-
upsert(sessions, session, (s) => s.account.sub === account.sub).map(
|
|
93
|
-
// Make sure to de-select any other selected session (if selected is
|
|
94
|
-
// true)
|
|
95
|
-
(s) =>
|
|
96
|
-
!selected || s === session || !s.selected
|
|
97
|
-
? s
|
|
98
|
-
: { ...s, selected: false },
|
|
99
|
-
),
|
|
100
|
-
)
|
|
101
|
-
},
|
|
102
|
-
[setSessions],
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const performRedirect = useCallback(
|
|
106
|
-
(url: string | URL) => {
|
|
107
|
-
// @TODO At this point, the request cannot be accepted/rejected anymore.
|
|
108
|
-
// We should probably change the app's state to something that indicates
|
|
109
|
-
// that in order to improve UX in case the user comes back to the app.
|
|
110
|
-
// This is currently ensured by the backend (through back-forward cache
|
|
111
|
-
// busting) but handling it here would provide a better UX.
|
|
112
|
-
|
|
113
|
-
window.location.href = String(url)
|
|
114
|
-
if (onRedirected) setTimeout(onRedirected)
|
|
115
|
-
},
|
|
116
|
-
[onRedirected],
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
const doSignIn = useSafeCallback(
|
|
120
|
-
async (data: Omit<SignInInput, 'locale'>, signal?: AbortSignal) => {
|
|
121
|
-
const response = await api.fetch(
|
|
122
|
-
'POST',
|
|
123
|
-
'/sign-in',
|
|
124
|
-
{ ...data, locale },
|
|
125
|
-
{ signal },
|
|
126
|
-
)
|
|
127
|
-
upsertSession(response)
|
|
128
|
-
},
|
|
129
|
-
[api, locale, upsertSession],
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
const doInitiatePasswordReset = useSafeCallback(
|
|
133
|
-
async (
|
|
134
|
-
data: Omit<InitiatePasswordResetInput, 'locale'>,
|
|
135
|
-
signal?: AbortSignal,
|
|
136
|
-
) => {
|
|
137
|
-
await api.fetch(
|
|
138
|
-
'POST',
|
|
139
|
-
'/reset-password-request',
|
|
140
|
-
{ ...data, locale },
|
|
141
|
-
{ signal },
|
|
142
|
-
)
|
|
143
|
-
},
|
|
144
|
-
[api, locale],
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
const doConfirmResetPassword = useSafeCallback(
|
|
148
|
-
async (data: ConfirmResetPasswordInput, signal?: AbortSignal) => {
|
|
149
|
-
await api.fetch('POST', '/reset-password-confirm', data, { signal })
|
|
150
|
-
},
|
|
151
|
-
[api],
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
const doValidateNewHandle = useSafeCallback(
|
|
155
|
-
async (data: VerifyHandleAvailabilityInput, signal?: AbortSignal) => {
|
|
156
|
-
await api.fetch('POST', '/verify-handle-availability', data, { signal })
|
|
157
|
-
},
|
|
158
|
-
[api],
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
const doSignUp = useSafeCallback(
|
|
162
|
-
async (data: Omit<SignUpInput, 'locale'>, signal?: AbortSignal) => {
|
|
163
|
-
const response = await api.fetch(
|
|
164
|
-
'POST',
|
|
165
|
-
'/sign-up',
|
|
166
|
-
{ ...data, locale },
|
|
167
|
-
{ signal },
|
|
168
|
-
)
|
|
169
|
-
upsertSession(response)
|
|
170
|
-
},
|
|
171
|
-
[api, locale, upsertSession],
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
const doAccept = useSafeCallback(
|
|
175
|
-
async (sub: string) => {
|
|
176
|
-
// If "remember me" was unchecked, we need to use the ephemeral token to
|
|
177
|
-
// authenticate the request.
|
|
178
|
-
const bearer = sessions.find((s) => s.account.sub === sub)?.ephemeralToken
|
|
179
|
-
const { url } = await api.fetch('POST', '/accept', { sub }, { bearer })
|
|
180
|
-
performRedirect(url)
|
|
181
|
-
},
|
|
182
|
-
[api, sessions, performRedirect],
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
const doReject = useSafeCallback(async () => {
|
|
186
|
-
const { url } = await api.fetch('POST', '/reject', {})
|
|
187
|
-
performRedirect(url)
|
|
188
|
-
}, [api, performRedirect])
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
sessions,
|
|
192
|
-
selectSub,
|
|
193
|
-
|
|
194
|
-
doSignIn,
|
|
195
|
-
doInitiatePasswordReset,
|
|
196
|
-
doConfirmResetPassword,
|
|
197
|
-
doValidateNewHandle,
|
|
198
|
-
doSignUp,
|
|
199
|
-
doAccept,
|
|
200
|
-
doReject,
|
|
201
|
-
}
|
|
202
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ForwardedRef,
|
|
3
|
-
useCallback,
|
|
4
|
-
useEffect,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
useRef,
|
|
7
|
-
useState,
|
|
8
|
-
} from 'react'
|
|
9
|
-
|
|
10
|
-
export type AsyncActionController = {
|
|
11
|
-
reset: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type UseAsyncActionOptions = {
|
|
15
|
-
ref?: ForwardedRef<AsyncActionController>
|
|
16
|
-
onLoading?: (loading: boolean) => void
|
|
17
|
-
onError?: (error: Error | undefined) => void
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function useAsyncAction(
|
|
21
|
-
fn: (signal: AbortSignal) => void | PromiseLike<void>,
|
|
22
|
-
{ ref, onLoading, onError }: UseAsyncActionOptions = {},
|
|
23
|
-
) {
|
|
24
|
-
const [loading, setLoading] = useState(false)
|
|
25
|
-
const [error, setError] = useState<Error | undefined>()
|
|
26
|
-
|
|
27
|
-
const doSetError = useCallback(
|
|
28
|
-
(error: Error | undefined) => {
|
|
29
|
-
setError(error)
|
|
30
|
-
onError?.(error)
|
|
31
|
-
},
|
|
32
|
-
[onError],
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
const doSetLoading = useCallback(
|
|
36
|
-
(loading: boolean) => {
|
|
37
|
-
setLoading(loading)
|
|
38
|
-
onLoading?.(loading)
|
|
39
|
-
},
|
|
40
|
-
[onLoading],
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
const controllerRef = useRef<AbortController>(null)
|
|
44
|
-
|
|
45
|
-
const resetRef = useRef<() => void>(null)
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
resetRef.current = () => {
|
|
48
|
-
controllerRef.current?.abort()
|
|
49
|
-
controllerRef.current = null
|
|
50
|
-
doSetError(undefined)
|
|
51
|
-
doSetLoading(false)
|
|
52
|
-
}
|
|
53
|
-
return () => {
|
|
54
|
-
resetRef.current = null
|
|
55
|
-
}
|
|
56
|
-
}, [doSetError, doSetLoading])
|
|
57
|
-
|
|
58
|
-
useImperativeHandle(
|
|
59
|
-
ref,
|
|
60
|
-
(): AsyncActionController => ({
|
|
61
|
-
reset: () => resetRef.current?.(),
|
|
62
|
-
}),
|
|
63
|
-
[],
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// Cancel pending action when unmounted
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
return () => {
|
|
69
|
-
controllerRef.current?.abort()
|
|
70
|
-
controllerRef.current = null
|
|
71
|
-
}
|
|
72
|
-
}, [])
|
|
73
|
-
|
|
74
|
-
const run = useCallback(async (): Promise<void> => {
|
|
75
|
-
// Cancel previous run
|
|
76
|
-
controllerRef.current?.abort()
|
|
77
|
-
|
|
78
|
-
doSetLoading(true)
|
|
79
|
-
doSetError(undefined)
|
|
80
|
-
|
|
81
|
-
const controller = new AbortController()
|
|
82
|
-
const { signal } = controller
|
|
83
|
-
|
|
84
|
-
controllerRef.current = controller
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
await fn(signal)
|
|
88
|
-
} catch (err) {
|
|
89
|
-
if (controller === controllerRef.current) {
|
|
90
|
-
doSetError(err instanceof Error ? err : new Error(String(err)))
|
|
91
|
-
} else {
|
|
92
|
-
if (!isAbortReason(signal, err)) {
|
|
93
|
-
console.warn('Async action error after abort', err)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} finally {
|
|
97
|
-
if (controller === controllerRef.current) {
|
|
98
|
-
controllerRef.current = null
|
|
99
|
-
doSetLoading(false)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
controller.abort()
|
|
103
|
-
}
|
|
104
|
-
}, [fn, doSetLoading, doSetError])
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
loading,
|
|
108
|
-
error,
|
|
109
|
-
run,
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isAbortReason(signal: AbortSignal, err: unknown): boolean {
|
|
114
|
-
return (
|
|
115
|
-
signal.aborted &&
|
|
116
|
-
(signal.reason === err ||
|
|
117
|
-
signal.reason === err?.['cause'] ||
|
|
118
|
-
(err instanceof DOMException && err.name === 'AbortError'))
|
|
119
|
-
)
|
|
120
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
const query =
|
|
4
|
-
typeof window === 'undefined'
|
|
5
|
-
? null
|
|
6
|
-
: window.matchMedia('(prefers-color-scheme: dark)')
|
|
7
|
-
|
|
8
|
-
export function useBrowserColorScheme() {
|
|
9
|
-
const [theme, setTheme] = useState<'light' | 'dark'>(
|
|
10
|
-
query?.matches ? 'dark' : 'light',
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (!query) return
|
|
15
|
-
|
|
16
|
-
const listener = () => {
|
|
17
|
-
setTheme(query.matches ? 'dark' : 'light')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
query.addEventListener('change', listener)
|
|
21
|
-
|
|
22
|
-
return () => {
|
|
23
|
-
query.removeEventListener('change', listener)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// @NOTE "query" is a global constant and does not need to be part of the
|
|
27
|
-
// array bellow:
|
|
28
|
-
}, [])
|
|
29
|
-
|
|
30
|
-
return theme
|
|
31
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
export const UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
4
|
-
export const LOWER = UPPER.toLowerCase() as Lowercase<typeof UPPER>
|
|
5
|
-
export const DIGITS = '0123456789'
|
|
6
|
-
|
|
7
|
-
export const ALPHANUMERIC = `${UPPER}${LOWER}${DIGITS}` as const
|
|
8
|
-
|
|
9
|
-
export type UseRandomStringOptions = BuildRandomStringOptions & {
|
|
10
|
-
prefix?: string
|
|
11
|
-
suffix?: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useRandomString(options?: UseRandomStringOptions) {
|
|
15
|
-
const [state, setState] = useState(() => buildRandomString(options))
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
setState(buildRandomString(options))
|
|
18
|
-
}, [options?.length, options?.alphabet])
|
|
19
|
-
|
|
20
|
-
return `${options?.prefix ?? ''}${state}${options?.suffix ?? ''}`
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type BuildRandomStringOptions = {
|
|
24
|
-
length?: number
|
|
25
|
-
alphabet?: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildRandomString({
|
|
29
|
-
length = 16,
|
|
30
|
-
alphabet = ALPHANUMERIC,
|
|
31
|
-
}: BuildRandomStringOptions = {}) {
|
|
32
|
-
return Array.from({ length }, () => getRandomCharFrom(alphabet)).join('')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getRandomCharFrom(alphabet: string) {
|
|
36
|
-
return alphabet.charAt((Math.random() * alphabet.length) | 0)
|
|
37
|
-
}
|