Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(user): delete user #1587

Merged
merged 4 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions next-tavla/app/(admin)/components/Footer/components/DeleteUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { SecondarySquareButton } from '@entur/button'
import { CloseIcon } from '@entur/icons'
import { Modal } from '@entur/modal'
import { Heading2, Label, Paragraph } from '@entur/typography'
import { useSearchParamsModal } from 'app/(admin)/hooks/useSearchParamsModal'
import Image from 'next/image'
import ducks from 'assets/illustrations/Ducks.png'
import { SubmitButton } from 'components/Form/SubmitButton'
import { TextField } from '@entur/form'
import { useFormState } from 'react-dom'

import { getFormFeedbackForField } from 'app/(admin)/utils'
import { deleteProfile } from './actions'

function DeleteUser() {
const [open, close] = useSearchParamsModal('deleteProfile')

const [state, action] = useFormState(deleteProfile, undefined)
return (
<>
<Modal
open={open}
size="small"
onDismiss={close}
closeLabel="Avbryt sletting"
className="flex flex-col justify-start items-center text-center"
>
<SecondarySquareButton
aria-label="Avbryt sletting"
className="ml-auto"
onClick={close}
>
<CloseIcon />
</SecondarySquareButton>
<Image src={ducks} alt="" className="h-1/2 w-1/2" />
<Heading2>Slett bruker</Heading2>
<Paragraph>
Alle dine private tavler, samt tavler tilknyttet
organisasjoner hvor du er det eneste medlemmet, vil også bli
slettet.
</Paragraph>
<form
action={action}
className="flex flex-col w-full gap-4"
aria-live="polite"
aria-relevant="all"
>
<Label className="font-medium text-left">
Bekreft ved å skrive inn din e-postadresse
</Label>
<TextField
name="email"
label="E-post"
type="text"
required
aria-required
className="w-full"
{...getFormFeedbackForField('email', state)}
/>

<SubmitButton
variant="primary"
width="fluid"
aria-label="Slett bruker"
>
Ja, slett
</SubmitButton>
</form>
</Modal>
</>
)
}

export { DeleteUser }
58 changes: 58 additions & 0 deletions next-tavla/app/(admin)/components/Footer/components/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use server'

import { TFormFeedback, getFormFeedbackForError } from 'app/(admin)/utils'
import {
deleteOrganization,
deleteUserBoards,
initializeAdminApp,
} from 'app/(admin)/utils/firebase'
import { getUserFromSessionCookie } from 'app/(admin)/utils/server'
import { getAuth } from 'firebase-admin/auth'
import { redirect } from 'next/navigation'
import { logout } from '../../Login/actions'
import { getOrganizationsForUser } from 'app/(admin)/actions'
import { TUserID } from 'types/settings'

initializeAdminApp()

export async function deleteProfile(
prevState: TFormFeedback | undefined,
data: FormData,
) {
const user = await getUserFromSessionCookie()
if (!user) return redirect('/')

const adminAuth = getAuth()

const email = data.get('email') as string

try {
const userRecord = await adminAuth.getUser(user.uid)

if (email !== userRecord.email)
return getFormFeedbackForError('auth/email-mismatch')
await deleteUserBoardsAndOrganizations(userRecord.uid)
await adminAuth.deleteUser(userRecord.uid)
await logout()
} catch (error) {
return getFormFeedbackForError()
}
}

async function deleteUserBoardsAndOrganizations(uid: TUserID) {
try {
const organizations = await getOrganizationsForUser()

await deleteUserBoards()
return Promise.all(
organizations.map((org) => {
if (org.owners?.includes(uid) && org.owners.length < 2) {
if (!org.id) return
deleteOrganization(org.id)
}
}),
)
} catch (error) {
return getFormFeedbackForError()
}
}
11 changes: 10 additions & 1 deletion next-tavla/app/(admin)/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { Heading3, Link as EnturLink, Paragraph } from '@entur/typography'
import Link from 'next/link'
import { ExternalIcon, GithubIcon } from '@entur/icons'
import { usePostHog } from 'posthog-js/react'
import { DeleteUser } from './components/DeleteUser'

function Footer() {
function Footer({ loggedIn }: { loggedIn: boolean }) {
const posthog = usePostHog()
return (
<footer className="eds-contrast">
Expand Down Expand Up @@ -81,6 +82,14 @@ function Footer() {
<ExternalIcon aria-hidden />
<GithubIcon size={25} aria-hidden />
</div>
{loggedIn && (
<div className="flex flex-row gap-1 items-center">
<EnturLink as={Link} href="?deleteProfile">
Slett bruker
</EnturLink>
<DeleteUser />
</div>
)}
</div>
</div>
</div>
Expand Down
14 changes: 14 additions & 0 deletions next-tavla/app/(admin)/utils/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getOrganizationIfUserHasAccess,
} from '../actions'
import { getFormFeedbackForError } from '.'
import { getPrivateBoardsForUser } from '../actions'

initializeAdminApp()

Expand Down Expand Up @@ -149,3 +150,16 @@ export async function deleteOrganizationBoard(
if (!access) throw 'auth/operation-not-allowed'
return firestore().collection('boards').doc(bid).delete()
}

export async function deleteUserBoards() {
const user = await getUser()
if (!user) return

const boards = await getPrivateBoardsForUser()

return Promise.all(
boards
.filter((board) => board !== undefined)
.map((board) => board?.id && deleteBoard(board.id)),
)
}
6 changes: 6 additions & 0 deletions next-tavla/app/(admin)/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ export function getFormFeedbackForError(
feedback: 'Skriv inn en e-postadresse.',
variant: 'warning',
}
case 'auth/email-mismatch':
return {
form_type: 'email',
feedback: 'E-postadressen stemmer ikke.',
variant: 'error',
}
case 'organization/not-found':
return {
form_type: 'general',
Expand Down
2 changes: 1 addition & 1 deletion next-tavla/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async function RootLayout({ children }: { children: ReactNode }) {
<PostHogPageView />
{children}
<FloatingContact />
<Footer />
<Footer loggedIn={loggedIn} />
</body>
</EnturToastProvider>
</PHProvider>
Expand Down