| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- import dayjs from 'dayjs'
- import { Edit, Unlink } from 'lucide-react'
- import Image from 'next/image'
- import Link from 'next/link'
- import { useRouter } from 'next/router'
- import { useEffect, useState } from 'react'
- import { toast } from 'sonner'
- import {
- Badge,
- Button,
- Card,
- CardContent,
- cn,
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- Tooltip,
- TooltipContent,
- TooltipTrigger,
- } from 'ui'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import {
- PageSection,
- PageSectionContent,
- PageSectionDescription,
- PageSectionMeta,
- PageSectionSummary,
- PageSectionTitle,
- } from 'ui-patterns/PageSection'
- import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
- import {
- ChangeEmailAddressForm,
- GitHubChangeEmailAddress,
- SSOChangeEmailAddress,
- } from './ChangeEmailAddress'
- import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
- import { useProfileIdentitiesQuery } from '@/data/profile/profile-identities-query'
- import { useUnlinkIdentityMutation } from '@/data/profile/profile-unlink-identity-mutation'
- import { BASE_PATH } from '@/lib/constants'
- const getProviderName = (provider: string) =>
- provider === 'github'
- ? 'GitHub'
- : provider.startsWith('sso')
- ? 'SSO'
- : provider.replaceAll('_', ' ')
- export const AccountIdentities = () => {
- const router = useRouter()
- const { data, isPending: isLoading, isSuccess } = useProfileIdentitiesQuery()
- const identities = data?.identities ?? []
- const isChangeExpired = data?.email_change_sent_at
- ? dayjs().utc().diff(dayjs(data?.email_change_sent_at).utc(), 'minute') > 10
- : false
- const [selectedProviderUnlink, setSelectedProviderUnlink] = useState<string>()
- const [selectedProviderUpdateEmail, setSelectedProviderUpdateEmail] = useState<string>()
- const { mutate: unlinkIdentity, isPending: isUnlinking } = useUnlinkIdentityMutation({
- onSuccess: () => {
- toast.success(
- `Successfully unlinked ${getProviderName(selectedProviderUnlink ?? '')} identity!`
- )
- setSelectedProviderUnlink(undefined)
- },
- })
- const [, message] = router.asPath.split('#message=')
- const onConfirmUnlinkIdentity = async () => {
- const identity = identities.find((i) => i.provider === selectedProviderUnlink)
- if (identity) unlinkIdentity(identity)
- }
- useEffect(() => {
- if (message) toast.success(message.replaceAll('+', ' '))
- }, [message])
- return (
- <PageSection>
- <PageSectionMeta>
- <PageSectionSummary>
- <PageSectionTitle>Account identities</PageSectionTitle>
- <PageSectionDescription>
- Manage the providers linked to your Briven account and update their details.
- </PageSectionDescription>
- </PageSectionSummary>
- </PageSectionMeta>
- <PageSectionContent>
- <Card>
- {isLoading && (
- <CardContent>
- <ShimmeringLoader />
- </CardContent>
- )}
- {isSuccess && (
- <div className="divide-y">
- {identities.map((identity) => {
- const { identity_id, provider } = identity
- const username = identity.identity_data?.user_name
- const providerName = getProviderName(provider)
- const iconKey =
- provider === 'github'
- ? 'github-icon'
- : provider === 'email'
- ? 'email-icon2'
- : 'saml-icon'
- return (
- <CardContent key={identity_id} className="flex justify-between items-center py-4">
- <div className="flex gap-x-4">
- <Image
- className={cn(iconKey === 'github-icon' ? 'dark:invert' : '')}
- src={`${BASE_PATH}/img/icons/${iconKey}.svg`}
- width={30}
- height={30}
- alt={`${identity.provider} icon`}
- />
- <div>
- <div className="flex items-center gap-x-2">
- <p className="text-sm capitalize">{providerName}</p>
- {provider === 'email' && data.new_email && !isChangeExpired && (
- <Tooltip>
- <TooltipTrigger className="flex items-center">
- <Badge variant="default">Pending change</Badge>
- </TooltipTrigger>
- <TooltipContent>Changing to {data.new_email}</TooltipContent>
- </Tooltip>
- )}
- </div>
- <p className="text-sm text-foreground-lighter">
- {!!username ? <span>{username} · </span> : null}
- {identity.email}
- </p>
- </div>
- </div>
- <div className="flex items-center gap-x-1">
- {provider === 'email' && (
- <Button asChild type="default">
- <Link href="/reset-password?type=change">Change password</Link>
- </Button>
- )}
- <ButtonTooltip
- type="text"
- icon={<Edit />}
- className="w-7"
- onClick={() => setSelectedProviderUpdateEmail(provider)}
- tooltip={{ content: { side: 'bottom', text: 'Update email address' } }}
- />
- {identities.length > 1 && (
- <ButtonTooltip
- type="text"
- icon={<Unlink />}
- className="w-7"
- onClick={() => setSelectedProviderUnlink(provider)}
- tooltip={{ content: { side: 'bottom', text: 'Unlink identity' } }}
- />
- )}
- </div>
- </CardContent>
- )
- })}
- </div>
- )}
- </Card>
- <Dialog
- open={!!selectedProviderUpdateEmail}
- onOpenChange={(open: boolean) => {
- if (!open) setSelectedProviderUpdateEmail(undefined)
- }}
- >
- <DialogContent>
- <DialogHeader className="border-b">
- <DialogTitle>
- {selectedProviderUpdateEmail !== 'email'
- ? `Updating email address for ${getProviderName(selectedProviderUpdateEmail ?? '')} identity`
- : 'Update email address'}
- </DialogTitle>
- </DialogHeader>
- {selectedProviderUpdateEmail === 'github' ? (
- <GitHubChangeEmailAddress />
- ) : selectedProviderUpdateEmail?.startsWith('sso') ? (
- <SSOChangeEmailAddress />
- ) : (
- <ChangeEmailAddressForm onClose={() => setSelectedProviderUpdateEmail(undefined)} />
- )}
- </DialogContent>
- </Dialog>
- <ConfirmationModal
- variant="warning"
- size="small"
- loading={isUnlinking}
- visible={!!selectedProviderUnlink}
- title={`Unlink ${getProviderName(selectedProviderUnlink ?? '')} identity`}
- onCancel={() => setSelectedProviderUnlink(undefined)}
- onConfirm={onConfirmUnlinkIdentity}
- confirmLabel="Unlink identity"
- confirmLabelLoading="Unlinking identity"
- alert={{
- base: { variant: 'warning' },
- title: `Confirm to disconnect your ${getProviderName(selectedProviderUnlink ?? '')} identity`,
- description: `After disconnecting, you will only be able to sign in via ${selectedProviderUnlink === 'github' ? 'email and password' : 'your GitHub identity'}`,
- }}
- />
- </PageSectionContent>
- </PageSection>
- )
- }
|