Skip to content

Commit

Permalink
feat(create): add organization to create board modal (#1631)
Browse files Browse the repository at this point in the history
* feat(create): add organization to create board modal

* fix(create): disable org field if no org is present

* fix(create): fetch latest organizations in dropdown

* feat(create): own component for name and org selector in create board modal

* refactor(create): own component for name and organization selector in create board modal

* feat(edit): title on board can be 50 chars

* refactor(create): check disabled organization to own variable

* chore(create): handle form with useFormState

* chore(create): error handling on create board modal

* fix(crate): error handling in create and move duplicateBoard function to own actions file

* fix(create): org set to null when private is selected

* refactor(create): rename variable
  • Loading branch information
emilielr authored Sep 13, 2024
1 parent be41641 commit 0493f02
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use client'
import { Dropdown } from '@entur/dropdown'
import { TextField, Checkbox } from '@entur/form'
import { AddIcon } from '@entur/icons'
import { Heading3, Paragraph, Label } from '@entur/typography'
import { useOrganizations } from 'app/(admin)/hooks/useOrganizations'
import { getFormFeedbackForField } from 'app/(admin)/utils'
import { HiddenInput } from 'components/Form/HiddenInput'
import { SubmitButton } from 'components/Form/SubmitButton'
import { useState } from 'react'
import { useFormState } from 'react-dom'
import { createBoard } from './actions'
import { FormError } from '../FormError'

function NameAndOrganizationSelector() {
const [state, action] = useFormState(createBoard, undefined)

const { organizations, selectedOrganization, setSelectedOrganization } =
useOrganizations()

const [isPersonal, setIsPersonal] = useState<boolean>(false)

const disableOrg = isPersonal || organizations().length == 0

return (
<form action={action} className="md:px-10">
<Heading3>Velg navn og organisasjon for tavlen</Heading3>
<Paragraph className="!mb-4">
Gi tavlen et navn og legg den til i en organisasjon. Velger du
en organisasjon vil alle i organisasjonen ha tilgang til tavlen.
</Paragraph>
<Label>Gi tavlen et navn</Label>
<TextField
size="medium"
label="Navn"
id="name"
name="name"
maxLength={50}
required
{...getFormFeedbackForField('name', state)}
/>

<div className="mt-4">
<Label>Legg til i en organisasjon</Label>
<Dropdown
items={organizations}
label="Dine organisasjoner"
selectedItem={isPersonal ? null : selectedOrganization}
onChange={setSelectedOrganization}
clearable
aria-required="true"
className="mb-4"
disabled={disableOrg}
{...getFormFeedbackForField('organization', state)}
/>
<Checkbox
checked={disableOrg}
onChange={() => {
setSelectedOrganization(null)
setIsPersonal(!isPersonal)
}}
name="personal"
>
Privat tavle
</Checkbox>
<HiddenInput
id="organization"
value={selectedOrganization?.value.id}
/>
</div>
<div className="mt-4">
<FormError {...getFormFeedbackForField('general', state)} />
</div>

<div className="flex flex-row mt-8 justify-end">
<SubmitButton variant="primary" className="max-sm:w-full">
Opprett tavle
<AddIcon />
</SubmitButton>
</div>
</form>
)
}

export { NameAndOrganizationSelector }
46 changes: 36 additions & 10 deletions tavla/app/(admin)/components/CreateBoard/actions.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
'use server'
import { getOrganizationIfUserHasAccess } from 'app/(admin)/actions'
import { getFormFeedbackForError } from 'app/(admin)/utils'
import { TFormFeedback, getFormFeedbackForError } from 'app/(admin)/utils'
import { initializeAdminApp } from 'app/(admin)/utils/firebase'
import { getUserFromSessionCookie } from 'app/(admin)/utils/server'
import admin, { firestore } from 'firebase-admin'
import { FirebaseError } from 'firebase/app'
import { redirect } from 'next/navigation'
import { TBoard, TOrganization, TOrganizationID } from 'types/settings'

initializeAdminApp()

export async function create(board: TBoard, oid?: TOrganizationID) {
export async function createBoard(
prevState: TFormFeedback | undefined,
data: FormData,
) {
const name = data.get('name') as string
if (!name) return getFormFeedbackForError('board/name-missing')

const oid = data.get('organization') as TOrganizationID
const personal = data.get('personal')
if (!oid && !personal)
return getFormFeedbackForError('create/organization-missing')

const board = {
tiles: [],
meta: {
title: name.substring(0, 50),
},
} as TBoard

const user = await getUserFromSessionCookie()
if (!user) return getFormFeedbackForError('auth/operation-not-allowed')

Expand All @@ -31,13 +50,20 @@ export async function create(board: TBoard, oid?: TOrganizationID) {
},
})

firestore()
.collection(oid ? 'organizations' : 'users')
.doc(oid ? String(oid) : String(user.uid))
.update({
[oid ? 'boards' : 'owner']: admin.firestore.FieldValue.arrayUnion(
createdBoard.id,
),
})
if (!createdBoard) return getFormFeedbackForError('firebase/general')

try {
firestore()
.collection(oid ? 'organizations' : 'users')
.doc(oid ? String(oid) : String(user.uid))
.update({
[oid ? 'boards' : 'owner']:
admin.firestore.FieldValue.arrayUnion(createdBoard.id),
})
} catch (e) {
if (e instanceof FirebaseError) return getFormFeedbackForError(e)
return getFormFeedbackForError('firebase/general')
}

redirect(`/edit/${createdBoard.id}`)
}
60 changes: 3 additions & 57 deletions tavla/app/(admin)/components/CreateBoard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,20 @@
'use client'
import { AddIcon } from '@entur/icons'
import { Modal } from '@entur/modal'
import { Heading3, Label } from '@entur/typography'
import { useState } from 'react'
import { TBoard } from 'types/settings'
import { TextField } from '@entur/form'
import {
TFormFeedback,
getFormFeedbackForError,
getFormFeedbackForField,
} from 'app/(admin)/utils'
import { SubmitButton } from 'components/Form/SubmitButton'
import { create } from './actions'
import { useSearchParamsModal } from 'app/(admin)/hooks/useSearchParamsModal'
import { NameAndOrganizationSelector } from './NameAndOrganizationSelector'

function CreateBoard() {
const [open, close] = useSearchParamsModal('board')

const [state, setFormError] = useState<TFormFeedback | undefined>()

return (
<>
<Modal
open={open}
size="medium"
className="flex flex-col items-center"
onDismiss={() => {
setFormError(undefined)
close()
}}
onDismiss={() => close()}
closeLabel="Avbryt opprettelse av tavle"
>
<form
action={async (data: FormData) => {
const name = data.get('name') as string
if (!name) {
return setFormError(
getFormFeedbackForError('board/name-missing'),
)
}
await create({
tiles: [],
meta: {
title: name.substring(0, 30),
},
} as TBoard)
}}
className="w-full md:w-3/4"
>
<Heading3>Hva skal tavlen hete?</Heading3>
<Label>Gi tavlen et navn</Label>
<TextField
size="medium"
label="Navn"
id="name"
name="name"
maxLength={30}
required
{...getFormFeedbackForField('name', state)}
/>
<div className="flex flex-row mt-8 justify-end">
<SubmitButton
variant="primary"
className="max-sm:w-full"
>
Opprett tavle
<AddIcon />
</SubmitButton>
</div>
</form>
<NameAndOrganizationSelector />
</Modal>
</>
)
Expand Down
44 changes: 44 additions & 0 deletions tavla/app/(admin)/edit/[id]/components/DuplicateBoard/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use server'
import { getOrganizationIfUserHasAccess } from 'app/(admin)/actions'
import { getFormFeedbackForError } from 'app/(admin)/utils'
import { initializeAdminApp } from 'app/(admin)/utils/firebase'
import { getUserFromSessionCookie } from 'app/(admin)/utils/server'
import admin, { firestore } from 'firebase-admin'
import { redirect } from 'next/navigation'
import { TBoard, TOrganization, TOrganizationID } from 'types/settings'

initializeAdminApp()

export async function duplicateBoard(board: TBoard, oid?: TOrganizationID) {
const user = await getUserFromSessionCookie()
if (!user) return getFormFeedbackForError('auth/operation-not-allowed')

let organization: TOrganization | undefined
if (oid) organization = await getOrganizationIfUserHasAccess(oid)

const createdBoard = await firestore()
.collection('boards')
.add({
...board,
meta: {
...board.meta,
fontSize:
board.meta?.fontSize ??
organization?.defaults?.font ??
'medium',
created: Date.now(),
dateModified: Date.now(),
},
})

firestore()
.collection(oid ? 'organizations' : 'users')
.doc(oid ? String(oid) : String(user.uid))
.update({
[oid ? 'boards' : 'owner']: admin.firestore.FieldValue.arrayUnion(
createdBoard.id,
),
})

redirect(`/edit/${createdBoard.id}`)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useToast } from '@entur/alert'
import { Button } from '@entur/button'
import { AddIcon } from '@entur/icons'
import { OverflowMenuItem } from '@entur/menu'
import { create } from 'app/(admin)/components/CreateBoard/actions'
import { TBoard, TOrganizationID } from 'types/settings'
import { duplicateBoard } from './actions'

function DuplicateBoard({
board,
Expand All @@ -18,7 +18,7 @@ function DuplicateBoard({
const { addToast } = useToast()
const handleSelect = async () => {
delete board.id
await create(
await duplicateBoard(
{
...board,
meta: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function MetaSettings({
className="w-full"
defaultValue={meta?.title ?? DEFAULT_BOARD_NAME}
label="Navn på tavlen"
maxLength={30}
maxLength={50}
/>
</div>

Expand Down
7 changes: 7 additions & 0 deletions tavla/app/(admin)/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ export function getFormFeedbackForError(
variant: 'error',
}
}
case 'firebase/general': {
return {
form_type: 'general',
feedback: 'En teknisk feil har oppstått. Vennligst prøv igjen.',
variant: 'error',
}
}
}

return {
Expand Down

0 comments on commit 0493f02

Please sign in to comment.