| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- import dayjs from 'dayjs'
- import { AlertCircle } from 'lucide-react'
- import { useState } from 'react'
- import { Alert, AlertDescription, AlertTitle, Button } from 'ui'
- import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
- import { AddNewFactorModal } from './AddNewFactorModal'
- import DeleteFactorModal from './DeleteFactorModal'
- import AlertError from '@/components/ui/AlertError'
- import { useMfaListFactorsQuery } from '@/data/profile/mfa-list-factors-query'
- import { DATETIME_FORMAT } from '@/lib/constants'
- export const TOTPFactors = () => {
- const [isAddNewFactorOpen, setIsAddNewFactorOpen] = useState(false)
- const [factorToBeDeleted, setFactorToBeDeleted] = useState<string | null>(null)
- const { data, isPending: isLoading, isError, isSuccess, error } = useMfaListFactorsQuery()
- return (
- <>
- <section className="space-y-3">
- <p className="text-sm text-foreground-light">
- Generate one-time passwords via authenticator apps like 1Password, Authy, etc. as a second
- factor to verify your identity during sign-in.
- </p>
- <div>
- {isLoading && <GenericSkeletonLoader />}
- {isError && (
- <AlertError error={error} subject="Failed to retrieve account security information" />
- )}
- {isSuccess && (
- <>
- {data.totp.length === 1 && (
- <Alert variant="default" className="mb-2">
- <AlertCircle className="h-4 w-4" />
- <AlertTitle>
- We recommend configuring two authenticator apps across different devices
- </AlertTitle>
- <AlertDescription className="flex flex-col gap-3">
- The two authenticator apps will serve as a backup for each other.
- </AlertDescription>
- </Alert>
- )}
- <div>
- {data.totp.map((factor) => {
- return (
- <div key={factor.id} className="flex flex-row justify-between py-2">
- <p className="text-sm text-foreground flex items-center space-x-2">
- <span className="text-foreground-light">Name:</span>{' '}
- <span>{factor.friendly_name ?? 'No name provided'}</span>
- </p>
- <div className="flex items-center gap-4">
- <p className="text-sm text-foreground-light">
- Added on {dayjs(factor.updated_at).format(DATETIME_FORMAT)}
- </p>
- <Button
- size="tiny"
- type="default"
- onClick={() => setFactorToBeDeleted(factor.id)}
- >
- Remove
- </Button>
- </div>
- </div>
- )
- })}
- </div>
- {data.totp.length < 2 ? (
- <>
- <div className="pt-2">
- <Button onClick={() => setIsAddNewFactorOpen(true)}>Add new app</Button>
- </div>
- </>
- ) : null}
- </>
- )}
- </div>
- </section>
- <AddNewFactorModal
- visible={isAddNewFactorOpen}
- onClose={() => setIsAddNewFactorOpen(false)}
- />
- <DeleteFactorModal
- visible={factorToBeDeleted !== null}
- factorId={factorToBeDeleted}
- lastFactorToBeDeleted={data?.totp.length === 1}
- onClose={() => setFactorToBeDeleted(null)}
- />
- </>
- )
- }
|