index.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import dayjs from 'dayjs'
  2. import { AlertCircle } from 'lucide-react'
  3. import { useState } from 'react'
  4. import { Alert, AlertDescription, AlertTitle, Button } from 'ui'
  5. import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
  6. import { AddNewFactorModal } from './AddNewFactorModal'
  7. import DeleteFactorModal from './DeleteFactorModal'
  8. import AlertError from '@/components/ui/AlertError'
  9. import { useMfaListFactorsQuery } from '@/data/profile/mfa-list-factors-query'
  10. import { DATETIME_FORMAT } from '@/lib/constants'
  11. export const TOTPFactors = () => {
  12. const [isAddNewFactorOpen, setIsAddNewFactorOpen] = useState(false)
  13. const [factorToBeDeleted, setFactorToBeDeleted] = useState<string | null>(null)
  14. const { data, isPending: isLoading, isError, isSuccess, error } = useMfaListFactorsQuery()
  15. return (
  16. <>
  17. <section className="space-y-3">
  18. <p className="text-sm text-foreground-light">
  19. Generate one-time passwords via authenticator apps like 1Password, Authy, etc. as a second
  20. factor to verify your identity during sign-in.
  21. </p>
  22. <div>
  23. {isLoading && <GenericSkeletonLoader />}
  24. {isError && (
  25. <AlertError error={error} subject="Failed to retrieve account security information" />
  26. )}
  27. {isSuccess && (
  28. <>
  29. {data.totp.length === 1 && (
  30. <Alert variant="default" className="mb-2">
  31. <AlertCircle className="h-4 w-4" />
  32. <AlertTitle>
  33. We recommend configuring two authenticator apps across different devices
  34. </AlertTitle>
  35. <AlertDescription className="flex flex-col gap-3">
  36. The two authenticator apps will serve as a backup for each other.
  37. </AlertDescription>
  38. </Alert>
  39. )}
  40. <div>
  41. {data.totp.map((factor) => {
  42. return (
  43. <div key={factor.id} className="flex flex-row justify-between py-2">
  44. <p className="text-sm text-foreground flex items-center space-x-2">
  45. <span className="text-foreground-light">Name:</span>{' '}
  46. <span>{factor.friendly_name ?? 'No name provided'}</span>
  47. </p>
  48. <div className="flex items-center gap-4">
  49. <p className="text-sm text-foreground-light">
  50. Added on {dayjs(factor.updated_at).format(DATETIME_FORMAT)}
  51. </p>
  52. <Button
  53. size="tiny"
  54. type="default"
  55. onClick={() => setFactorToBeDeleted(factor.id)}
  56. >
  57. Remove
  58. </Button>
  59. </div>
  60. </div>
  61. )
  62. })}
  63. </div>
  64. {data.totp.length < 2 ? (
  65. <>
  66. <div className="pt-2">
  67. <Button onClick={() => setIsAddNewFactorOpen(true)}>Add new app</Button>
  68. </div>
  69. </>
  70. ) : null}
  71. </>
  72. )}
  73. </div>
  74. </section>
  75. <AddNewFactorModal
  76. visible={isAddNewFactorOpen}
  77. onClose={() => setIsAddNewFactorOpen(false)}
  78. />
  79. <DeleteFactorModal
  80. visible={factorToBeDeleted !== null}
  81. factorId={factorToBeDeleted}
  82. lastFactorToBeDeleted={data?.totp.length === 1}
  83. onClose={() => setFactorToBeDeleted(null)}
  84. />
  85. </>
  86. )
  87. }