oauth-callback-screen.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { useMemo } from 'react'
  2. import { Loader2, Send, Shield, UserRound, type LucideIcon } from 'lucide-react'
  3. import { useTranslation } from 'react-i18next'
  4. import { SiGithub, SiLinux, SiWechat } from 'react-icons/si'
  5. import { AuthLayout } from '../auth-layout'
  6. type OAuthCallbackScreenProps = {
  7. provider: string
  8. mode: 'login' | 'bind'
  9. }
  10. type ProviderMeta = {
  11. label: string
  12. Icon: LucideIcon | ((props: { className?: string }) => React.JSX.Element)
  13. }
  14. const providerDictionary: Record<string, ProviderMeta> = {
  15. github: {
  16. label: 'GitHub',
  17. Icon: (props: { className?: string }) => (
  18. <SiGithub className={props.className} focusable='false' />
  19. ),
  20. },
  21. oidc: { label: 'OIDC', Icon: Shield },
  22. linuxdo: {
  23. label: 'LinuxDO',
  24. Icon: (props: { className?: string }) => (
  25. <SiLinux className={props.className} focusable='false' />
  26. ),
  27. },
  28. telegram: { label: 'Telegram', Icon: Send },
  29. wechat: {
  30. label: 'WeChat',
  31. Icon: (props: { className?: string }) => (
  32. <SiWechat className={props.className} focusable='false' />
  33. ),
  34. },
  35. }
  36. export function OAuthCallbackScreen({
  37. provider,
  38. mode,
  39. }: OAuthCallbackScreenProps) {
  40. const { t } = useTranslation()
  41. const { label, Icon } = useMemo(() => {
  42. const normalized = provider?.toLowerCase() ?? ''
  43. return (
  44. providerDictionary[normalized] || {
  45. label: 'account',
  46. Icon: UserRound,
  47. }
  48. )
  49. }, [provider])
  50. const providerLabel = t(label)
  51. const isBindMode = mode === 'bind'
  52. const headline = isBindMode
  53. ? t('Binding your {{provider}} account', { provider: providerLabel })
  54. : t('Signing you in with {{provider}}', { provider: providerLabel })
  55. const description = isBindMode
  56. ? t('Hang tight while we securely link this account to your profile.')
  57. : t('Hang tight while we finish connecting your account.')
  58. const secondaryNote = isBindMode
  59. ? t(
  60. 'You can close this tab once the binding completes or a success message appears in the original window.'
  61. )
  62. : t(
  63. "You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds."
  64. )
  65. return (
  66. <AuthLayout>
  67. <div className='w-full space-y-8'>
  68. <div className='flex flex-col items-center space-y-4 text-center'>
  69. <div className='bg-muted flex h-16 w-16 items-center justify-center rounded-full'>
  70. <Icon className='h-8 w-8' />
  71. </div>
  72. <div className='space-y-2'>
  73. <h2 className='text-center text-2xl font-semibold tracking-tight'>
  74. {headline}
  75. </h2>
  76. <p className='text-muted-foreground text-sm sm:text-base'>
  77. {description}
  78. </p>
  79. </div>
  80. </div>
  81. <div className='space-y-4 text-center'>
  82. <div className='flex items-center justify-center gap-2 text-sm font-medium'>
  83. <Loader2 className='h-4 w-4 animate-spin' />
  84. <span>{t('Processing OAuth response...')}</span>
  85. </div>
  86. <p className='text-muted-foreground text-sm'>{secondaryNote}</p>
  87. <p className='text-muted-foreground text-xs'>
  88. {t(
  89. 'This may take a few moments while we validate the request and update your session.'
  90. )}
  91. </p>
  92. </div>
  93. </div>
  94. </AuthLayout>
  95. )
  96. }