@atproto/oauth-provider 0.5.2 → 0.6.1

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.
Files changed (310) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/account/account-manager.d.ts +7 -5
  3. package/dist/account/account-manager.d.ts.map +1 -1
  4. package/dist/account/account-manager.js +34 -25
  5. package/dist/account/account-manager.js.map +1 -1
  6. package/dist/account/account-store.d.ts +7 -0
  7. package/dist/account/account-store.d.ts.map +1 -1
  8. package/dist/account/account-store.js.map +1 -1
  9. package/dist/account/account.d.ts +1 -11
  10. package/dist/account/account.d.ts.map +1 -1
  11. package/dist/account/{sign-up-data.d.ts → sign-up-input.d.ts} +3 -3
  12. package/dist/account/sign-up-input.d.ts.map +1 -0
  13. package/dist/account/{sign-up-data.js → sign-up-input.js} +3 -3
  14. package/dist/account/sign-up-input.js.map +1 -0
  15. package/dist/assets/assets-middleware.d.ts +2 -0
  16. package/dist/assets/assets-middleware.d.ts.map +1 -1
  17. package/dist/assets/assets-middleware.js +12 -14
  18. package/dist/assets/assets-middleware.js.map +1 -1
  19. package/dist/lib/csp/index.d.ts +5 -6
  20. package/dist/lib/csp/index.d.ts.map +1 -1
  21. package/dist/lib/csp/index.js +14 -11
  22. package/dist/lib/csp/index.js.map +1 -1
  23. package/dist/lib/hcaptcha.d.ts +15 -12
  24. package/dist/lib/hcaptcha.d.ts.map +1 -1
  25. package/dist/lib/hcaptcha.js +11 -7
  26. package/dist/lib/hcaptcha.js.map +1 -1
  27. package/dist/lib/html/build-document.d.ts +2 -2
  28. package/dist/lib/html/build-document.d.ts.map +1 -1
  29. package/dist/lib/html/build-document.js +11 -7
  30. package/dist/lib/html/build-document.js.map +1 -1
  31. package/dist/lib/html/html.d.ts.map +1 -1
  32. package/dist/lib/html/html.js +10 -13
  33. package/dist/lib/html/html.js.map +1 -1
  34. package/dist/lib/html/util.d.ts +0 -1
  35. package/dist/lib/html/util.d.ts.map +1 -1
  36. package/dist/lib/html/util.js +0 -4
  37. package/dist/lib/html/util.js.map +1 -1
  38. package/dist/lib/http/response.d.ts +3 -1
  39. package/dist/lib/http/response.d.ts.map +1 -1
  40. package/dist/lib/http/response.js +3 -0
  41. package/dist/lib/http/response.js.map +1 -1
  42. package/dist/lib/http/security-headers.d.ts +48 -0
  43. package/dist/lib/http/security-headers.d.ts.map +1 -0
  44. package/dist/lib/http/security-headers.js +62 -0
  45. package/dist/lib/http/security-headers.js.map +1 -0
  46. package/dist/lib/util/type.d.ts +8 -0
  47. package/dist/lib/util/type.d.ts.map +1 -1
  48. package/dist/lib/util/type.js.map +1 -1
  49. package/dist/oauth-hooks.d.ts +4 -25
  50. package/dist/oauth-hooks.d.ts.map +1 -1
  51. package/dist/oauth-provider.js +2 -2
  52. package/dist/oauth-provider.js.map +1 -1
  53. package/dist/output/backend-data.d.ts +4 -0
  54. package/dist/output/backend-data.d.ts.map +1 -0
  55. package/dist/output/backend-data.js +19 -0
  56. package/dist/output/backend-data.js.map +1 -0
  57. package/dist/output/build-authorize-data.d.ts +3 -19
  58. package/dist/output/build-authorize-data.d.ts.map +1 -1
  59. package/dist/output/build-authorize-data.js.map +1 -1
  60. package/dist/output/build-customization-data.d.ts +11 -18
  61. package/dist/output/build-customization-data.d.ts.map +1 -1
  62. package/dist/output/build-customization-data.js +1 -1
  63. package/dist/output/build-customization-data.js.map +1 -1
  64. package/dist/output/build-error-data.d.ts +3 -0
  65. package/dist/output/build-error-data.d.ts.map +1 -0
  66. package/dist/output/build-error-data.js +10 -0
  67. package/dist/output/build-error-data.js.map +1 -0
  68. package/dist/output/build-error-payload.d.ts +2 -1
  69. package/dist/output/build-error-payload.d.ts.map +1 -1
  70. package/dist/output/build-error-payload.js.map +1 -1
  71. package/dist/output/output-manager.d.ts +10 -4
  72. package/dist/output/output-manager.d.ts.map +1 -1
  73. package/dist/output/output-manager.js +68 -39
  74. package/dist/output/output-manager.js.map +1 -1
  75. package/dist/output/send-web-page.d.ts +6 -10
  76. package/dist/output/send-web-page.d.ts.map +1 -1
  77. package/dist/output/send-web-page.js +27 -47
  78. package/dist/output/send-web-page.js.map +1 -1
  79. package/dist/signer/signed-token-payload.d.ts +3 -3
  80. package/dist/signer/signer.d.ts +2 -2
  81. package/package.json +8 -41
  82. package/src/account/account-manager.ts +55 -34
  83. package/src/account/account-store.ts +8 -0
  84. package/src/account/account.ts +1 -14
  85. package/src/account/{sign-up-data.ts → sign-up-input.ts} +2 -2
  86. package/src/assets/assets-middleware.ts +11 -17
  87. package/src/lib/csp/index.ts +16 -13
  88. package/src/lib/hcaptcha.ts +14 -10
  89. package/src/lib/html/build-document.ts +15 -8
  90. package/src/lib/html/html.ts +11 -18
  91. package/src/lib/html/util.ts +0 -4
  92. package/src/lib/http/response.ts +9 -1
  93. package/src/lib/http/security-headers.ts +91 -0
  94. package/src/lib/util/type.ts +18 -0
  95. package/src/oauth-hooks.ts +4 -25
  96. package/src/oauth-provider.ts +2 -2
  97. package/src/output/backend-data.ts +18 -0
  98. package/src/output/build-authorize-data.ts +3 -26
  99. package/src/output/build-customization-data.ts +2 -13
  100. package/src/output/build-error-data.ts +8 -0
  101. package/src/output/build-error-payload.ts +4 -2
  102. package/src/output/output-manager.ts +86 -47
  103. package/src/output/send-web-page.ts +29 -58
  104. package/tsconfig.backend.json +1 -2
  105. package/tsconfig.backend.tsbuildinfo +1 -1
  106. package/tsconfig.json +1 -5
  107. package/.linguirc +0 -57
  108. package/dist/account/sign-up-data.d.ts.map +0 -1
  109. package/dist/account/sign-up-data.js.map +0 -1
  110. package/dist/assets/app/bundle-manifest.json +0 -614
  111. package/dist/assets/app/index-DZHZ9kCP.js +0 -36
  112. package/dist/assets/app/index-DZHZ9kCP.js.map +0 -1
  113. package/dist/assets/app/main-B_dNxQo_.js +0 -4
  114. package/dist/assets/app/main-B_dNxQo_.js.map +0 -1
  115. package/dist/assets/app/main-Dr6y26KY.css +0 -3
  116. package/dist/assets/app/main-Dr6y26KY.js +0 -306
  117. package/dist/assets/app/main-Dr6y26KY.js.map +0 -1
  118. package/dist/assets/app/messages-6_mYuGzB.js +0 -4
  119. package/dist/assets/app/messages-6_mYuGzB.js.map +0 -1
  120. package/dist/assets/app/messages-7wdeBTpD.js +0 -4
  121. package/dist/assets/app/messages-7wdeBTpD.js.map +0 -1
  122. package/dist/assets/app/messages-B-YFoWKc.js +0 -4
  123. package/dist/assets/app/messages-B-YFoWKc.js.map +0 -1
  124. package/dist/assets/app/messages-B10DUOE-.js +0 -4
  125. package/dist/assets/app/messages-B10DUOE-.js.map +0 -1
  126. package/dist/assets/app/messages-B4AwFEeZ.js +0 -4
  127. package/dist/assets/app/messages-B4AwFEeZ.js.map +0 -1
  128. package/dist/assets/app/messages-BDP8MyEC.js +0 -4
  129. package/dist/assets/app/messages-BDP8MyEC.js.map +0 -1
  130. package/dist/assets/app/messages-BIS87lxQ.js +0 -4
  131. package/dist/assets/app/messages-BIS87lxQ.js.map +0 -1
  132. package/dist/assets/app/messages-BI_Wbjdt.js +0 -4
  133. package/dist/assets/app/messages-BI_Wbjdt.js.map +0 -1
  134. package/dist/assets/app/messages-BMAouhRx.js +0 -4
  135. package/dist/assets/app/messages-BMAouhRx.js.map +0 -1
  136. package/dist/assets/app/messages-BdckMnJj.js +0 -4
  137. package/dist/assets/app/messages-BdckMnJj.js.map +0 -1
  138. package/dist/assets/app/messages-BgBLzc46.js +0 -4
  139. package/dist/assets/app/messages-BgBLzc46.js.map +0 -1
  140. package/dist/assets/app/messages-BobD78yK.js +0 -4
  141. package/dist/assets/app/messages-BobD78yK.js.map +0 -1
  142. package/dist/assets/app/messages-BtThT9UZ.js +0 -4
  143. package/dist/assets/app/messages-BtThT9UZ.js.map +0 -1
  144. package/dist/assets/app/messages-BwKHkbeh.js +0 -4
  145. package/dist/assets/app/messages-BwKHkbeh.js.map +0 -1
  146. package/dist/assets/app/messages-C417YUvA.js +0 -4
  147. package/dist/assets/app/messages-C417YUvA.js.map +0 -1
  148. package/dist/assets/app/messages-C4CxO4bO.js +0 -4
  149. package/dist/assets/app/messages-C4CxO4bO.js.map +0 -1
  150. package/dist/assets/app/messages-C5vd04e6.js +0 -4
  151. package/dist/assets/app/messages-C5vd04e6.js.map +0 -1
  152. package/dist/assets/app/messages-CAri2Wnz.js +0 -4
  153. package/dist/assets/app/messages-CAri2Wnz.js.map +0 -1
  154. package/dist/assets/app/messages-CPtWTZeG.js +0 -4
  155. package/dist/assets/app/messages-CPtWTZeG.js.map +0 -1
  156. package/dist/assets/app/messages-CiaM5zm8.js +0 -4
  157. package/dist/assets/app/messages-CiaM5zm8.js.map +0 -1
  158. package/dist/assets/app/messages-CkL-L2R6.js +0 -4
  159. package/dist/assets/app/messages-CkL-L2R6.js.map +0 -1
  160. package/dist/assets/app/messages-Cy_4XLNe.js +0 -4
  161. package/dist/assets/app/messages-Cy_4XLNe.js.map +0 -1
  162. package/dist/assets/app/messages-D5_ad-Eo.js +0 -4
  163. package/dist/assets/app/messages-D5_ad-Eo.js.map +0 -1
  164. package/dist/assets/app/messages-DChMl9mT.js +0 -4
  165. package/dist/assets/app/messages-DChMl9mT.js.map +0 -1
  166. package/dist/assets/app/messages-DWX-DIfv.js +0 -4
  167. package/dist/assets/app/messages-DWX-DIfv.js.map +0 -1
  168. package/dist/assets/app/messages-DgfsOphe.js +0 -4
  169. package/dist/assets/app/messages-DgfsOphe.js.map +0 -1
  170. package/dist/assets/app/messages-Dj5B_DR6.js +0 -4
  171. package/dist/assets/app/messages-Dj5B_DR6.js.map +0 -1
  172. package/dist/assets/app/messages-Dwzqo4eA.js +0 -4
  173. package/dist/assets/app/messages-Dwzqo4eA.js.map +0 -1
  174. package/dist/assets/app/messages-ESCIXJR7.js +0 -4
  175. package/dist/assets/app/messages-ESCIXJR7.js.map +0 -1
  176. package/dist/assets/app/messages-dglB2edb.js +0 -4
  177. package/dist/assets/app/messages-dglB2edb.js.map +0 -1
  178. package/dist/assets/app/messages-e_ClRrWc.js +0 -4
  179. package/dist/assets/app/messages-e_ClRrWc.js.map +0 -1
  180. package/dist/assets/app/messages-evvDxmrP.js +0 -4
  181. package/dist/assets/app/messages-evvDxmrP.js.map +0 -1
  182. package/dist/assets/app/messages-pPbdLb5B.js +0 -4
  183. package/dist/assets/app/messages-pPbdLb5B.js.map +0 -1
  184. package/dist/assets/app/messages-tJv8gHL2.js +0 -4
  185. package/dist/assets/app/messages-tJv8gHL2.js.map +0 -1
  186. package/dist/assets/app/messages-vLRVEw96.js +0 -4
  187. package/dist/assets/app/messages-vLRVEw96.js.map +0 -1
  188. package/dist/assets/asset.d.ts +0 -9
  189. package/dist/assets/asset.d.ts.map +0 -1
  190. package/dist/assets/asset.js +0 -3
  191. package/dist/assets/asset.js.map +0 -1
  192. package/dist/assets/index.d.ts +0 -5
  193. package/dist/assets/index.d.ts.map +0 -1
  194. package/dist/assets/index.js +0 -78
  195. package/dist/assets/index.js.map +0 -1
  196. package/rollup.config.js +0 -98
  197. package/src/assets/app/app.tsx +0 -43
  198. package/src/assets/app/backend-data.ts +0 -27
  199. package/src/assets/app/backend-types.ts +0 -66
  200. package/src/assets/app/components/forms/button-toggle-visibility.tsx +0 -43
  201. package/src/assets/app/components/forms/button.tsx +0 -60
  202. package/src/assets/app/components/forms/fieldset.tsx +0 -55
  203. package/src/assets/app/components/forms/form-card-async.tsx +0 -103
  204. package/src/assets/app/components/forms/form-card.tsx +0 -49
  205. package/src/assets/app/components/forms/input-checkbox.tsx +0 -78
  206. package/src/assets/app/components/forms/input-container.tsx +0 -107
  207. package/src/assets/app/components/forms/input-email-address.tsx +0 -65
  208. package/src/assets/app/components/forms/input-new-password.tsx +0 -62
  209. package/src/assets/app/components/forms/input-password.tsx +0 -87
  210. package/src/assets/app/components/forms/input-text.tsx +0 -82
  211. package/src/assets/app/components/forms/input-token.tsx +0 -94
  212. package/src/assets/app/components/forms/wizard-card.tsx +0 -116
  213. package/src/assets/app/components/layouts/layout-title-page.tsx +0 -77
  214. package/src/assets/app/components/layouts/layout-welcome.tsx +0 -73
  215. package/src/assets/app/components/utils/account-identifier.tsx +0 -23
  216. package/src/assets/app/components/utils/account-image.tsx +0 -33
  217. package/src/assets/app/components/utils/admonition.tsx +0 -52
  218. package/src/assets/app/components/utils/client-name.tsx +0 -45
  219. package/src/assets/app/components/utils/error-card.tsx +0 -93
  220. package/src/assets/app/components/utils/error-message.tsx +0 -88
  221. package/src/assets/app/components/utils/help-card.tsx +0 -46
  222. package/src/assets/app/components/utils/icons.tsx +0 -88
  223. package/src/assets/app/components/utils/link-anchor.tsx +0 -28
  224. package/src/assets/app/components/utils/link-title.tsx +0 -26
  225. package/src/assets/app/components/utils/multi-lang-string.tsx +0 -56
  226. package/src/assets/app/components/utils/password-strength-label.tsx +0 -37
  227. package/src/assets/app/components/utils/password-strength-meter.tsx +0 -58
  228. package/src/assets/app/components/utils/url-viewer.tsx +0 -73
  229. package/src/assets/app/cookies.ts +0 -11
  230. package/src/assets/app/hooks/use-api.ts +0 -178
  231. package/src/assets/app/hooks/use-async-action.ts +0 -120
  232. package/src/assets/app/hooks/use-bound-dispatch.ts +0 -5
  233. package/src/assets/app/hooks/use-browser-color-scheme.ts +0 -31
  234. package/src/assets/app/hooks/use-csrf-token.ts +0 -5
  235. package/src/assets/app/hooks/use-random-string.ts +0 -37
  236. package/src/assets/app/hooks/use-stepper.ts +0 -87
  237. package/src/assets/app/index.html +0 -182
  238. package/src/assets/app/lib/api.ts +0 -289
  239. package/src/assets/app/lib/clsx.ts +0 -6
  240. package/src/assets/app/lib/json-client.ts +0 -94
  241. package/src/assets/app/lib/password.ts +0 -98
  242. package/src/assets/app/lib/ref.ts +0 -17
  243. package/src/assets/app/lib/util.ts +0 -13
  244. package/src/assets/app/locales/an/messages.po +0 -490
  245. package/src/assets/app/locales/ast/messages.po +0 -490
  246. package/src/assets/app/locales/ca/messages.po +0 -490
  247. package/src/assets/app/locales/da/messages.po +0 -490
  248. package/src/assets/app/locales/de/messages.po +0 -490
  249. package/src/assets/app/locales/el/messages.po +0 -490
  250. package/src/assets/app/locales/en/messages.po +0 -490
  251. package/src/assets/app/locales/en-GB/messages.po +0 -490
  252. package/src/assets/app/locales/es/messages.po +0 -490
  253. package/src/assets/app/locales/eu/messages.po +0 -490
  254. package/src/assets/app/locales/fi/messages.po +0 -490
  255. package/src/assets/app/locales/fr/messages.po +0 -490
  256. package/src/assets/app/locales/ga/messages.po +0 -490
  257. package/src/assets/app/locales/gl/messages.po +0 -490
  258. package/src/assets/app/locales/hi/messages.po +0 -490
  259. package/src/assets/app/locales/hu/messages.po +0 -490
  260. package/src/assets/app/locales/ia/messages.po +0 -490
  261. package/src/assets/app/locales/id/messages.po +0 -490
  262. package/src/assets/app/locales/it/messages.po +0 -490
  263. package/src/assets/app/locales/ja/messages.po +0 -490
  264. package/src/assets/app/locales/km/messages.po +0 -490
  265. package/src/assets/app/locales/ko/messages.po +0 -490
  266. package/src/assets/app/locales/load.ts +0 -8
  267. package/src/assets/app/locales/locale-context.ts +0 -19
  268. package/src/assets/app/locales/locale-provider.tsx +0 -112
  269. package/src/assets/app/locales/locale-selector.tsx +0 -58
  270. package/src/assets/app/locales/locales.ts +0 -168
  271. package/src/assets/app/locales/ne/messages.po +0 -490
  272. package/src/assets/app/locales/nl/messages.po +0 -490
  273. package/src/assets/app/locales/pl/messages.po +0 -490
  274. package/src/assets/app/locales/pt-BR/messages.po +0 -490
  275. package/src/assets/app/locales/ro/messages.po +0 -490
  276. package/src/assets/app/locales/ru/messages.po +0 -490
  277. package/src/assets/app/locales/sv/messages.po +0 -490
  278. package/src/assets/app/locales/th/messages.po +0 -490
  279. package/src/assets/app/locales/tr/messages.po +0 -490
  280. package/src/assets/app/locales/uk/messages.po +0 -490
  281. package/src/assets/app/locales/vi/messages.po +0 -490
  282. package/src/assets/app/locales/zh-CN/messages.po +0 -490
  283. package/src/assets/app/locales/zh-HK/messages.po +0 -490
  284. package/src/assets/app/locales/zh-TW/messages.po +0 -490
  285. package/src/assets/app/main.css +0 -33
  286. package/src/assets/app/main.tsx +0 -44
  287. package/src/assets/app/views/authorize/accept/accept-form.tsx +0 -150
  288. package/src/assets/app/views/authorize/accept/accept-view.tsx +0 -70
  289. package/src/assets/app/views/authorize/authorize-view.tsx +0 -180
  290. package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +0 -88
  291. package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +0 -80
  292. package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +0 -127
  293. package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +0 -242
  294. package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +0 -116
  295. package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +0 -145
  296. package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +0 -142
  297. package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +0 -51
  298. package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +0 -287
  299. package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +0 -108
  300. package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +0 -158
  301. package/src/assets/app/views/authorize/welcome/welcome-view.tsx +0 -56
  302. package/src/assets/app/views/error/error-view.tsx +0 -31
  303. package/src/assets/asset.ts +0 -9
  304. package/src/assets/index.ts +0 -86
  305. package/tailwind.config.js +0 -31
  306. package/tsconfig.frontend.json +0 -11
  307. package/tsconfig.frontend.tsbuildinfo +0 -1
  308. package/tsconfig.tools.json +0 -8
  309. package/tsconfig.tools.tsbuildinfo +0 -1
  310. package/vite.config.mjs +0 -16
@@ -1,73 +0,0 @@
1
- import { JSX } from 'react'
2
- import { CustomizationData } from '../../backend-types.ts'
3
- import { clsx } from '../../lib/clsx.ts'
4
- import { Override } from '../../lib/util.ts'
5
- import { LocaleSelector } from '../../locales/locale-selector.tsx'
6
- import { LinkAnchor } from '../utils/link-anchor.tsx'
7
-
8
- export type LayoutWelcomeProps = Override<
9
- JSX.IntrinsicElements['div'],
10
- {
11
- customizationData: CustomizationData | undefined
12
- title?: string
13
- }
14
- >
15
-
16
- export function LayoutWelcome({
17
- customizationData: { logo, name, links } = {},
18
- title = name,
19
-
20
- // div
21
- className,
22
- children,
23
- ...props
24
- }: LayoutWelcomeProps) {
25
- return (
26
- <div
27
- {...props}
28
- className={clsx(
29
- 'min-h-screen w-full',
30
- 'flex items-center justify-center flex-col',
31
- 'bg-white text-slate-900',
32
- 'dark:bg-slate-900 dark:text-slate-100',
33
- className,
34
- )}
35
- >
36
- {title && <title>{title}</title>}
37
-
38
- <main className="w-full overflow-hidden flex-grow flex flex-col items-center justify-center p-6">
39
- {logo && (
40
- <img
41
- src={logo}
42
- alt={name || `Logo`}
43
- aria-hidden
44
- className="w-16 h-16 md:w-24 md:h-24 mb-4 md:mb-8"
45
- />
46
- )}
47
-
48
- {name && (
49
- <h1 className="text-2xl md:text-4xl mb-4 md:mb-8 mx-4 text-center font-bold">
50
- {name}
51
- </h1>
52
- )}
53
-
54
- {children}
55
- </main>
56
-
57
- <nav className="w-full overflow-hidden border-t border-t-slate-200 dark:border-t-slate-700 flex flex-wrap justify-center content-center">
58
- {links?.map((link, i) => (
59
- <LinkAnchor
60
- key={i}
61
- link={link}
62
- className="m-2 md:m-4 text-xs md:text-sm text-brand hover:underline"
63
- />
64
- ))}
65
-
66
- <LocaleSelector
67
- className="m-1 md:m-2 text-xs md:text-sm"
68
- key="localeSelector"
69
- />
70
- </nav>
71
- </div>
72
- )
73
- }
@@ -1,23 +0,0 @@
1
- import { JSX } from 'react'
2
- import { Account } from '../../backend-types.ts'
3
- import { Override } from '../../lib/util.ts'
4
-
5
- export type AccountIdentifierProps = Override<
6
- Omit<JSX.IntrinsicElements['b'], 'children'>,
7
- {
8
- account: Account
9
- }
10
- >
11
-
12
- export function AccountIdentifier({
13
- account,
14
-
15
- // b
16
- ...props
17
- }: AccountIdentifierProps) {
18
- return (
19
- <b {...props}>
20
- {account.preferred_username || account.email || account.sub}
21
- </b>
22
- )
23
- }
@@ -1,33 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
- import { AccountIcon } from './icons.tsx'
3
-
4
- export type AccountIconProps = {
5
- src?: string
6
- alt: string
7
- }
8
-
9
- export function AccountImage({ src, alt }: AccountIconProps) {
10
- const [errored, setErrored] = useState(false)
11
-
12
- useEffect(() => {
13
- setErrored(false)
14
- }, [src])
15
-
16
- return src && !errored ? (
17
- <img
18
- aria-hidden
19
- crossOrigin="anonymous"
20
- src={src}
21
- alt={alt}
22
- className="-ml-1 w-6 h-6 rounded-full"
23
- onError={() => setErrored(true)}
24
- />
25
- ) : (
26
- <div
27
- aria-hidden
28
- className="h-6 w-6 text-white bg-brand rounded-full border-solid border-2 border-brand overflow-hidden"
29
- >
30
- <AccountIcon className="-mx-1 -mb-1" />
31
- </div>
32
- )
33
- }
@@ -1,52 +0,0 @@
1
- import { JSX, memo } from 'react'
2
- import { clsx } from '../../lib/clsx.ts'
3
- import { Override } from '../../lib/util.ts'
4
- import { AlertIcon, EyeIcon } from './icons.tsx'
5
-
6
- export type AdmonitionProps = Override<
7
- JSX.IntrinsicElements['div'],
8
- {
9
- role: 'alert' | 'status' | 'info'
10
- }
11
- >
12
-
13
- export const Admonition = memo(function Admonition({
14
- role = 'alert',
15
- children,
16
- className,
17
- ...props
18
- }: AdmonitionProps) {
19
- return (
20
- <div
21
- {...props}
22
- role={role}
23
- className={clsx(
24
- 'flex flex-row',
25
- 'gap-2',
26
- 'p-3',
27
- 'rounded-lg',
28
- 'border',
29
- 'border-gray-300 dark:border-gray-700',
30
- role === 'alert' && 'bg-error text-error-c',
31
- className,
32
- )}
33
- >
34
- {role === 'info' ? (
35
- <EyeIcon
36
- aria-hidden
37
- className={clsx('fill-current h-6 w-6', 'text-brand')}
38
- />
39
- ) : (
40
- <AlertIcon
41
- aria-hidden
42
- className={clsx(
43
- 'fill-current h-6 w-6',
44
- role === 'alert' ? 'text-inherit' : 'text-brand',
45
- )}
46
- />
47
- )}
48
-
49
- <div className="flex flex-1 flex-col">{children}</div>
50
- </div>
51
- )
52
- })
@@ -1,45 +0,0 @@
1
- import { Trans } from '@lingui/react/macro'
2
- import { JSX } from 'react'
3
- import type { OAuthClientMetadata } from '@atproto/oauth-types'
4
- import { Override } from '../../lib/util.ts'
5
- import { UrlViewer } from './url-viewer.tsx'
6
-
7
- export type ClientNameProps = Override<
8
- Omit<JSX.IntrinsicElements['span'], 'children'>,
9
- {
10
- clientId: string
11
- clientMetadata: OAuthClientMetadata
12
- clientTrusted: boolean
13
- }
14
- >
15
-
16
- export function ClientName({
17
- clientId,
18
- clientMetadata,
19
- clientTrusted,
20
-
21
- // span
22
- ...attrs
23
- }: ClientNameProps) {
24
- if (clientTrusted && clientMetadata.client_name) {
25
- return <span {...attrs}>{clientMetadata.client_name}</span>
26
- }
27
-
28
- // @NOTE: not using isOAuthClientIdLoopback & isOAuthClientIdDiscoverable from
29
- // @atproto/oauth-types here because 1) we don't need to validate here and 2)
30
- // we prefer not to import un-necessary code to improve bundle size.
31
-
32
- if (clientId.startsWith('http://')) {
33
- return (
34
- <span {...attrs}>
35
- <Trans>An application on your device</Trans>
36
- </span>
37
- )
38
- }
39
-
40
- if (clientId.startsWith('https://')) {
41
- return <UrlViewer {...attrs} url={clientId} path />
42
- }
43
-
44
- return <span {...attrs}>{clientId}</span>
45
- }
@@ -1,93 +0,0 @@
1
- import { Trans } from '@lingui/react/macro'
2
- import { memo, useEffect, useMemo, useState } from 'react'
3
- import { useRandomString } from '../../hooks/use-random-string.ts'
4
- import { Api } from '../../lib/api.ts'
5
- import { JsonErrorResponse } from '../../lib/json-client.ts'
6
- import { Override } from '../../lib/util.ts'
7
- import { Admonition, AdmonitionProps } from './admonition.tsx'
8
- import { ErrorMessage } from './error-message.tsx'
9
-
10
- export type ErrorCardProps = Override<
11
- Omit<AdmonitionProps, 'role'>,
12
- {
13
- error: unknown
14
- }
15
- >
16
- export const ErrorCard = memo(function ErrorCard({
17
- error,
18
-
19
- // Admonition
20
- children,
21
- onClick,
22
- onKeyDown,
23
- ...props
24
- }: ErrorCardProps) {
25
- const [inputCount, setInputCount] = useState(0)
26
- // Every 5th input will toggle showing the details
27
- const showDetails = ((inputCount / 5) | 0) % 2 === 1
28
-
29
- const detailsDivId = useRandomString('error-card-')
30
-
31
- const parsedError = useMemo(
32
- () =>
33
- error instanceof JsonErrorResponse
34
- ? // Already parsed:
35
- error
36
- : // If "error" is a json object, try parsing it as a JsonErrorResponse:
37
- Api.parseError(error) ?? error,
38
- [error],
39
- )
40
-
41
- useEffect(() => {
42
- // For debugging purposes
43
- console.warn('Displayed error details:', parsedError)
44
-
45
- // Reset the input count when the error changes
46
- setInputCount(0)
47
- }, [parsedError])
48
-
49
- return (
50
- <Admonition
51
- role="alert"
52
- aria-controls={detailsDivId}
53
- tabIndex={0}
54
- onKeyDown={(event) => {
55
- onKeyDown?.(event)
56
- if (!event.defaultPrevented) {
57
- setInputCount((c) => c + 1)
58
- }
59
- }}
60
- onClick={(event) => {
61
- onClick?.(event)
62
- if (!event.defaultPrevented) {
63
- setInputCount((c) => c + 1)
64
- }
65
- }}
66
- {...props}
67
- >
68
- <ErrorMessage error={parsedError} />
69
-
70
- {children && <div className="mt-2">{children}</div>}
71
-
72
- <div hidden={!showDetails} id={detailsDivId} aria-hidden={!showDetails}>
73
- {parsedError instanceof JsonErrorResponse ? (
74
- <dl className="mt-2 grid grid-cols-[auto,1fr] gap-x-2 text-sm">
75
- <dt className="font-semibold">
76
- <Trans>Code</Trans>
77
- </dt>
78
- <dd>
79
- <code>{parsedError.error}</code>
80
- </dd>
81
-
82
- <dt className="font-semibold">
83
- <Trans>Description</Trans>
84
- </dt>
85
- <dd>{parsedError.description}</dd>
86
- </dl>
87
- ) : (
88
- <pre className="text-xs">{JSON.stringify(parsedError, null, 2)}</pre>
89
- )}
90
- </div>
91
- </Admonition>
92
- )
93
- })
@@ -1,88 +0,0 @@
1
- import { Trans } from '@lingui/react/macro'
2
- import { ReactNode, memo } from 'react'
3
- import {
4
- AccessDeniedError,
5
- EmailTakenError,
6
- HandleUnavailableError,
7
- InvalidCredentialsError,
8
- InvalidInviteCodeError,
9
- InvalidRequestError,
10
- RequestExpiredError,
11
- SecondAuthenticationFactorRequiredError,
12
- UnknownRequestUriError,
13
- } from '../../lib/api.ts'
14
- import { JsonErrorResponse } from '../../lib/json-client.ts'
15
-
16
- export type ApiErrorMessageProps = {
17
- error: unknown
18
- }
19
-
20
- export const ErrorMessage = memo(function ErrorMessage({
21
- error,
22
- }: ApiErrorMessageProps): ReactNode {
23
- // Matches the order of the error checks in the API's parseError method (must
24
- // be from most specific to least specific to avoid unreachable code paths).
25
-
26
- if (error instanceof SecondAuthenticationFactorRequiredError) {
27
- return <Trans>A second authentication factor is required</Trans>
28
- }
29
-
30
- if (error instanceof InvalidCredentialsError) {
31
- return <Trans>Wrong identifier or password</Trans>
32
- }
33
-
34
- if (error instanceof InvalidInviteCodeError) {
35
- return <Trans>The invite code is not valid</Trans>
36
- }
37
-
38
- if (error instanceof HandleUnavailableError) {
39
- switch (error.reason) {
40
- case 'syntax':
41
- return <Trans>The handle is invalid</Trans>
42
- case 'domain':
43
- return <Trans>The domain name is not allowed</Trans>
44
- case 'slur':
45
- return <Trans>The handle contains inappropriate language</Trans>
46
- case 'taken':
47
- if (error.description === 'Reserved handle') {
48
- return <Trans>This handle is reserved</Trans>
49
- }
50
- return <Trans>The handle is already in use</Trans>
51
- default:
52
- return <Trans>That handle cannot be used</Trans>
53
- }
54
- }
55
-
56
- if (error instanceof EmailTakenError) {
57
- return <Trans>This email is already used</Trans>
58
- }
59
-
60
- if (
61
- error instanceof UnknownRequestUriError ||
62
- error instanceof RequestExpiredError
63
- ) {
64
- return <Trans>This sign-in session has expired</Trans>
65
- }
66
-
67
- if (error instanceof InvalidRequestError) {
68
- return (
69
- <Trans>
70
- The data you submitted is invalid. Please check the form and try again.
71
- </Trans>
72
- )
73
- }
74
-
75
- if (error instanceof AccessDeniedError) {
76
- return (
77
- <Trans>
78
- This authorization request has been denied. Please try again.
79
- </Trans>
80
- )
81
- }
82
-
83
- if (error instanceof JsonErrorResponse) {
84
- return <Trans>Unexpected server response</Trans>
85
- }
86
-
87
- return <Trans>An unknown error occurred</Trans>
88
- })
@@ -1,46 +0,0 @@
1
- import { Trans } from '@lingui/react/macro'
2
- import { JSX } from 'react'
3
- import { LinkDefinition } from '../../backend-types.ts'
4
- import { clsx } from '../../lib/clsx.ts'
5
- import { Override } from '../../lib/util.ts'
6
-
7
- export type HelpCardProps = Override<
8
- Omit<JSX.IntrinsicElements['p'], 'children'>,
9
- {
10
- links?: readonly LinkDefinition[]
11
- }
12
- >
13
-
14
- export function HelpCard({
15
- links,
16
-
17
- className,
18
- ...props
19
- }: HelpCardProps) {
20
- const helpLink = links?.find((l) => l.rel === 'help')
21
-
22
- if (!helpLink) return null
23
-
24
- return (
25
- <p
26
- {...props}
27
- className={clsx(
28
- 'text-sm rounded-md bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-400 p-3',
29
- className,
30
- )}
31
- >
32
- <Trans>
33
- Having trouble?{' '}
34
- <a
35
- role="link"
36
- href={helpLink.href}
37
- rel={helpLink.rel}
38
- target="_blank"
39
- className="text-brand"
40
- >
41
- <Trans>Contact support</Trans>
42
- </a>
43
- </Trans>
44
- </p>
45
- )
46
- }
@@ -1,88 +0,0 @@
1
- import type { FunctionComponent, JSX } from 'react'
2
- import { Override } from '../../lib/util.ts'
3
-
4
- export type IconProps = Override<
5
- Omit<JSX.IntrinsicElements['svg'], 'viewBox' | 'children' | 'xmlns'>,
6
- {
7
- /**
8
- * The title of the icon, used for accessibility.
9
- */
10
- title?: string
11
- }
12
- >
13
-
14
- const makeSvgComponent = (path: string, displayName: string) => {
15
- const SvgComponent: FunctionComponent<IconProps> = ({ title, ...props }) => (
16
- <svg
17
- xmlns="http://www.w3.org/2000/svg"
18
- viewBox="0 0 24 24"
19
- {...props}
20
- aria-hidden={!title}
21
- >
22
- {title && <title>{title}</title>}
23
- <path
24
- fill="currentColor"
25
- fillRule="evenodd"
26
- clipRule="evenodd"
27
- d={path}
28
- ></path>
29
- </svg>
30
- )
31
- SvgComponent.displayName = displayName
32
- return SvgComponent
33
- }
34
-
35
- export const AccountIcon = makeSvgComponent(
36
- 'M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z',
37
- 'AccountIcon',
38
- )
39
-
40
- export const AlertIcon = makeSvgComponent(
41
- 'M11.14 4.494a.995.995 0 0 1 1.72 0l7.001 12.008a.996.996 0 0 1-.86 1.498H4.999a.996.996 0 0 1-.86-1.498L11.14 4.494Zm3.447-1.007c-1.155-1.983-4.019-1.983-5.174 0L2.41 15.494C1.247 17.491 2.686 20 4.998 20h14.004c2.312 0 3.751-2.509 2.587-4.506L14.587 3.487ZM13 9.019a1 1 0 1 0-2 0v2.994a1 1 0 1 0 2 0V9.02Zm-1 4.731a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5Z',
42
- 'AlertIcon',
43
- )
44
-
45
- export const AtSymbolIcon = makeSvgComponent(
46
- 'M12 4a8 8 0 1 0 4.21 14.804 1 1 0 0 1 1.054 1.7A9.958 9.958 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 1.104-.27 2.31-.949 3.243-.716.984-1.849 1.6-3.331 1.465a4.207 4.207 0 0 1-2.93-1.585c-.94 1.21-2.388 1.94-3.985 1.715-2.53-.356-4.04-2.91-3.682-5.458.358-2.547 2.514-4.586 5.044-4.23.905.127 1.68.536 2.286 1.126a1 1 0 0 1 1.964.368l-.515 3.545v.002a2.222 2.222 0 0 0 1.999 2.526c.75.068 1.212-.21 1.533-.65.358-.493.566-1.245.566-2.067a8 8 0 0 0-8-8Zm-.112 5.13c-1.195-.168-2.544.819-2.784 2.529-.24 1.71.784 3.03 1.98 3.198 1.195.168 2.543-.819 2.784-2.529.24-1.71-.784-3.03-1.98-3.198Z',
47
- 'AtSymbolIcon',
48
- )
49
-
50
- export const CaretRightIcon = makeSvgComponent(
51
- 'M8.293 3.293a1 1 0 0 1 1.414 0l8 8a1 1 0 0 1 0 1.414l-8 8a1 1 0 0 1-1.414-1.414L15.586 12 8.293 4.707a1 1 0 0 1 0-1.414Z',
52
- 'CaretRightIcon',
53
- )
54
-
55
- export const CheckMarkIcon = makeSvgComponent(
56
- 'M21.59 3.193a1 1 0 0 1 .217 1.397l-11.706 16a1 1 0 0 1-1.429.193l-6.294-5a1 1 0 1 1 1.244-1.566l5.48 4.353 11.09-15.16a1 1 0 0 1 1.398-.217Z',
57
- 'CheckMarkIcon',
58
- )
59
-
60
- export const EmailIcon = makeSvgComponent(
61
- 'M4.568 4h14.864c.252 0 .498 0 .706.017.229.019.499.063.77.201a2 2 0 0 1 .874.874c.138.271.182.541.201.77.017.208.017.454.017.706v10.864c0 .252 0 .498-.017.706a2.022 2.022 0 0 1-.201.77 2 2 0 0 1-.874.874 2.022 2.022 0 0 1-.77.201c-.208.017-.454.017-.706.017H4.568c-.252 0-.498 0-.706-.017a2.022 2.022 0 0 1-.77-.201 2 2 0 0 1-.874-.874 2.022 2.022 0 0 1-.201-.77C2 17.93 2 17.684 2 17.432V6.568c0-.252 0-.498.017-.706.019-.229.063-.499.201-.77a2 2 0 0 1 .874-.874c.271-.138.541-.182.77-.201C4.07 4 4.316 4 4.568 4Zm.456 2L12 11.708 18.976 6H5.024ZM20 7.747l-6.733 5.509a2 2 0 0 1-2.534 0L4 7.746V17.4a8.187 8.187 0 0 0 .011.589h.014c.116.01.278.011.575.011h14.8a8.207 8.207 0 0 0 .589-.012v-.013c.01-.116.011-.279.011-.575V7.747Z',
62
- 'EmailIcon',
63
- )
64
-
65
- export const EyeIcon = makeSvgComponent(
66
- 'M12 6.5c3.79 0 7.17 2.13 8.82 5.5-1.65 3.37-5.02 5.5-8.82 5.5S4.83 15.37 3.18 12C4.83 8.63 8.21 6.5 12 6.5m0-2C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5m0 5c1.38 0 2.5 1.12 2.5 2.5s-1.12 2.5-2.5 2.5-2.5-1.12-2.5-2.5 1.12-2.5 2.5-2.5m0-2c-2.48 0-4.5 2.02-4.5 4.5s2.02 4.5 4.5 4.5 4.5-2.02 4.5-4.5-2.02-4.5-4.5-4.5',
67
- 'EyeIcon',
68
- )
69
-
70
- export const EyeSlashIcon = makeSvgComponent(
71
- 'M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2.41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6m-1.07 1.14L13 9.21c.57.25 1.03.71 1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07.14M2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45zm7.5 7.5 2.61 2.61c-.04.01-.08.02-.12.02-1.38 0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13m-3.4-3.4 1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53',
72
- 'EyeSlashIcon',
73
- )
74
-
75
- export const LockIcon = makeSvgComponent(
76
- 'M7 7a5 5 0 0 1 10 0v2h1a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-9a2 2 0 0 1 2-2h1V7Zm-1 4v9h12v-9H6Zm9-2H9V7a3 3 0 1 1 6 0v2Zm-3 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0v-3a1 1 0 0 1 1-1Z',
77
- 'LockIcon',
78
- )
79
-
80
- export const TokenIcon = makeSvgComponent(
81
- 'M4 5.5a.5.5 0 0 0-.5.5v2.535a.5.5 0 0 0 .25.433A3.498 3.498 0 0 1 5.5 12a3.498 3.498 0 0 1-1.75 3.032.5.5 0 0 0-.25.433V18a.5.5 0 0 0 .5.5h16a.5.5 0 0 0 .5-.5v-2.535a.5.5 0 0 0-.25-.433A3.498 3.498 0 0 1 18.5 12a3.5 3.5 0 0 1 1.75-3.032.5.5 0 0 0 .25-.433V6a.5.5 0 0 0-.5-.5H4ZM2.5 6A1.5 1.5 0 0 1 4 4.5h16A1.5 1.5 0 0 1 21.5 6v3.17a.5.5 0 0 1-.333.472 2.501 2.501 0 0 0 0 4.716.5.5 0 0 1 .333.471V18a1.5 1.5 0 0 1-1.5 1.5H4A1.5 1.5 0 0 1 2.5 18v-3.17a.5.5 0 0 1 .333-.472 2.501 2.501 0 0 0 0-4.716.5.5 0 0 1-.333-.471V6Zm12 2a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Zm0 4a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0Z',
82
- 'TokenIcon',
83
- )
84
-
85
- export const XMarkIcon = makeSvgComponent(
86
- 'M4.293 4.293a1 1 0 0 1 1.414 0L12 10.586l6.293-6.293a1 1 0 1 1 1.414 1.414L13.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414L12 13.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L10.586 12 4.293 5.707a1 1 0 0 1 0-1.414Z',
87
- 'XMarkIcon',
88
- )
@@ -1,28 +0,0 @@
1
- import { JSX } from 'react'
2
- import { LinkDefinition } from '../../backend-types.ts'
3
- import { Override } from '../../lib/util.ts'
4
- import { LinkTitle } from './link-title.tsx'
5
-
6
- export type LinkAnchorProps = Override<
7
- JSX.IntrinsicElements['a'],
8
- {
9
- link: LinkDefinition
10
- }
11
- >
12
- export function LinkAnchor({
13
- link,
14
-
15
- // a
16
- children = <LinkTitle link={link} />,
17
- role = 'link',
18
- target = '_blank',
19
- href = link.href,
20
- rel = link.rel,
21
- ...props
22
- }: LinkAnchorProps) {
23
- return (
24
- <a {...props} role={role} target={target} href={href} rel={rel}>
25
- {children}
26
- </a>
27
- )
28
- }
@@ -1,26 +0,0 @@
1
- import { Trans } from '@lingui/react/macro'
2
- import { LinkDefinition } from '../../backend-types.ts'
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,56 +0,0 @@
1
- import { useLingui } from '@lingui/react/macro'
2
- import { ReactNode } from 'react'
3
- import type { LocalizedString } from '../../backend-types.ts'
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 { i18n } = useLingui()
15
- return (
16
- findMatchingString(value, i18n.locale) ??
17
- fallback ??
18
- (typeof value === 'string' ? value : value.en)
19
- )
20
- }
21
-
22
- /**
23
- * Only returns a string if it matches the desired locale.
24
- */
25
- function findMatchingString(
26
- value: LocalizedString,
27
- locale: string,
28
- ): string | undefined {
29
- switch (typeof value) {
30
- case 'string':
31
- // By convention, string values are in english ("en")
32
- if (locale.startsWith('en')) return value
33
- break
34
-
35
- case 'object': {
36
- // Exact match
37
- const localeMatch = value[locale]
38
- if (typeof localeMatch === 'string') return localeMatch
39
-
40
- // Fallback to language match
41
- const lang = locale.split('-')[0]
42
- const langMatch = value[lang]
43
- if (typeof langMatch === 'string') return langMatch
44
-
45
- // Fallback to any locale from same language (e.g. "pt-PT" -> "pt-BR")
46
- for (const k in value) {
47
- if (k.startsWith(`${lang}-`)) {
48
- const fallbackMatch = value[k]
49
- if (typeof fallbackMatch === 'string') return fallbackMatch
50
- }
51
- }
52
- }
53
- }
54
-
55
- return undefined
56
- }