Skip to content

Commit bfa82b1

Browse files
authored
feat(app router): migrating organizations to app router (#1338)
* feat(accessibility): exporting more detailed metadata * feat(app router): added client wrappers for toolip and toast * feat(app router): added 'use client' directive and migrated to next/navigation router * chore(app router): removed bulletbadge * feat(app router): moved organizations to app router * feat(app router): migrated member administration to form actions * feat(forms): added submit-button and hidden input components, bumped react, added react-dom * feat(form actions): utilizing useFormState, deleted unused api route * fix(app router): removed organizations page * fix(app router): removed unnecessary 'use client' directives * chore(contrast): added contrast class to root layout instead of using components * chore(invite user): moved error handling * feat(remove user): added error handling for remove user button * chore(app router): added 'use client' directive to submitbutton * fix(code quality): removed unnecessary promise nesting * fix(code quality): minor enhancement of return types * chore(type naming): Props => TProps * chore(code conventions): moved helper components to bottom of file * fix(code quality): disable button after form submission
1 parent 7346966 commit bfa82b1

File tree

29 files changed

+367
-346
lines changed

29 files changed

+367
-346
lines changed
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.root {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 1em;
5+
padding: 1em;
6+
margin: 0 5em;
7+
}
8+
9+
.pageContainer {
10+
margin: 0 auto;
11+
max-width: 120rem;
12+
}
13+
14+
.organization {
15+
display: grid;
16+
grid-template-columns: 1fr 1fr;
17+
gap: 8em;
18+
}

next-tavla/app/(admin)/layout.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { OrganizationIcon, UserIcon } from '@entur/icons'
22
import TavlaLogo from 'assets/logos/Tavla-white.svg'
3+
import { Metadata } from 'next'
34
import Image from 'next/image'
45
import Link from 'next/link'
56
import { ReactNode } from 'react'
7+
import classes from './admin.module.css'
8+
9+
export const metadata: Metadata = {
10+
title: 'Mine organisasjoner | Entur Tavla',
11+
}
612

713
function AdminLayout({ children }: { children: ReactNode }) {
814
return (
9-
<>
15+
<div className={classes.pageContainer}>
1016
<div className="flexRow justifyBetween alignCenter p-4">
1117
<Link href="/">
1218
<Image src={TavlaLogo} height={32} alt="Tavla logo" />
@@ -28,7 +34,7 @@ function AdminLayout({ children }: { children: ReactNode }) {
2834
</div>
2935
</div>
3036
{children}
31-
</>
37+
</div>
3238
)
3339
}
3440

next-tavla/app/(admin)/organizations/[id]/page.tsx

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,36 @@
1-
import classes from 'styles/pages/admin.module.css'
1+
import classes from '../../admin.module.css'
22
import { cookies } from 'next/headers'
33
import {
44
getOrganization,
5+
getOrganizationById,
6+
getOrganizationUsers,
7+
getUsersWithEmailsByUids,
58
initializeAdminApp,
69
verifySession,
710
} from 'Admin/utils/firebase'
811
import { permanentRedirect } from 'next/navigation'
9-
import { Organization } from 'Admin/scenarios/Organization'
12+
import { Metadata } from 'next'
13+
import { Heading1 } from '@entur/typography'
14+
import { UploadLogo } from 'Admin/scenarios/Organization/components/UploadLogo'
15+
import { MemberAdministration } from 'Admin/scenarios/Organization/components/MemberAdministration'
1016

1117
initializeAdminApp()
1218

13-
async function EditOrganizationPage({ params }: { params: { id: string } }) {
19+
type TProps = {
20+
params: { id: string }
21+
}
22+
23+
export async function generateMetadata({ params }: TProps): Promise<Metadata> {
24+
const { id } = params
25+
26+
const organization = await getOrganizationById(id)
27+
28+
return {
29+
title: `${organization.name} | Entur Tavla`,
30+
}
31+
}
32+
33+
async function EditOrganizationPage({ params }: TProps) {
1434
const { id } = params
1535

1636
const session = cookies().get('session')
@@ -23,9 +43,20 @@ async function EditOrganizationPage({ params }: { params: { id: string } }) {
2343
if (!organization || !organization?.owners?.includes(user.uid))
2444
return <div>Du har ikke tilgang til denne organisasjonen</div>
2545

46+
const userIds = await getOrganizationUsers(id ?? '')
47+
const members = await getUsersWithEmailsByUids(userIds)
48+
2649
return (
2750
<div className={classes.root}>
28-
<Organization user={user} organization={organization} />
51+
<Heading1>{organization.name}</Heading1>
52+
<div className={classes.organization}>
53+
<UploadLogo organization={organization} />
54+
<MemberAdministration
55+
members={members}
56+
uid={user.uid}
57+
oid={organization.id}
58+
/>
59+
</div>
2960
</div>
3061
)
3162
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import classes from '../admin.module.css'
2+
import { Organizations } from 'Admin/scenarios/Organizations'
3+
import { getOrganizationsWithUser, verifySession } from 'Admin/utils/firebase'
4+
import { cookies } from 'next/headers'
5+
import { redirect } from 'next/navigation'
6+
7+
async function OrganizationsPage() {
8+
const session = cookies().get('session')
9+
const user = await verifySession(session?.value)
10+
11+
if (!user) redirect('/#login')
12+
13+
const organizations = await getOrganizationsWithUser(user.uid)
14+
15+
return (
16+
<div className={classes.root}>
17+
<Organizations organizations={organizations} userId={user.uid} />
18+
</div>
19+
)
20+
}
21+
22+
export default OrganizationsPage

next-tavla/app/layout.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ export const metadata: Metadata = {
4040
function RootLayout({ children }: { children: ReactNode }) {
4141
return (
4242
<html lang="nb">
43-
<body>{children}</body>
43+
<body>
44+
<div className="eds-contrast">{children}</div>
45+
</body>
4446
</html>
4547
)
4648
}

next-tavla/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@
6363
"@graphql-codegen/typescript-operations": "3.0.2",
6464
"@types/lodash": "4.14.191",
6565
"@types/node": "18.15.5",
66-
"@types/react": "18.2.37",
66+
"@types/react": "18.2.38",
67+
"@types/react-dom": "18.2.17",
6768
"@typescript-eslint/eslint-plugin": "5.57.1",
6869
"@typescript-eslint/parser": "5.57.1",
6970
"dependency-cruiser": "13.0.3",

next-tavla/pages/api/organization/[oid]/members/invite.ts

-54
This file was deleted.

next-tavla/pages/organizations/index.tsx

-55
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
'use client'
2-
import { ReactNode } from 'react'
3-
import { Contrast as EnturContrast } from '@entur/layout'
2+
import { ContrastProps, Contrast as EnturContrast } from '@entur/layout'
43

5-
function Contrast({ children }: { children: ReactNode }) {
6-
return <EnturContrast>{children}</EnturContrast>
4+
function Contrast(props: ContrastProps<'div'>) {
5+
return <EnturContrast {...props}>{props.children}</EnturContrast>
76
}
87

98
export { Contrast }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client'
2+
import { ReactNode } from 'react'
3+
import { ToastProvider as EnturToastProvider } from '@entur/alert'
4+
5+
function ToastProvider({ children }: { children: ReactNode }) {
6+
return <EnturToastProvider>{children}</EnturToastProvider>
7+
}
8+
9+
export { ToastProvider }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use client'
2+
import { Tooltip as EnturTooltip, TooltipProps } from '@entur/tooltip'
3+
4+
function Tooltip(props: TooltipProps) {
5+
return <EnturTooltip {...props}>{props.children}</EnturTooltip>
6+
}
7+
8+
export { Tooltip }

next-tavla/src/Admin/scenarios/Organization/components/MemberAdministration/InviteUser.tsx

+14-45
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,36 @@
1-
import { Button } from '@entur/button'
1+
'use client'
22
import { TextField } from '@entur/form'
33
import { AddIcon } from '@entur/icons'
4-
import { useToggle } from 'hooks/useToggle'
5-
import { SyntheticEvent } from 'react'
6-
import { TOrganizationID, TUser } from 'types/settings'
4+
import { TOrganizationID } from 'types/settings'
75
import classes from './styles.module.css'
8-
import { FeedbackCode, useFormFeedback } from 'hooks/useFormFeedback'
9-
import { fetchInviteUserToOrganizationByEmail } from 'Admin/utils/fetch'
6+
import { inviteUserAction } from 'Admin/utils/formActions'
7+
import { useFormState } from 'react-dom'
8+
import { HiddenInput } from 'components/Form/HiddenInput'
9+
import { getFormStateProps } from 'utils/formStatuses'
10+
import { SubmitButton } from 'components/Form/SubmitButton'
1011

11-
function InviteUser({
12-
oid,
13-
addMember,
14-
}: {
15-
oid?: TOrganizationID
16-
addMember: (member: TUser) => void
17-
}) {
18-
const [isLoading, enableLoading, disableLoading] = useToggle()
19-
const { setFeedback, clearFeedback, getTextFieldProps } = useFormFeedback()
20-
21-
const submitHandler = (event: SyntheticEvent) => {
22-
event.preventDefault()
23-
clearFeedback()
24-
25-
const { email } = event.currentTarget as unknown as {
26-
email: HTMLInputElement
27-
}
28-
29-
enableLoading()
30-
31-
fetchInviteUserToOrganizationByEmail(email.value, oid)
32-
.then((response) => {
33-
disableLoading()
34-
addMember({ email: email.value })
35-
response.json().then((data: { feedbackCode: FeedbackCode }) => {
36-
setFeedback(data.feedbackCode)
37-
})
38-
})
39-
.catch(() => {
40-
disableLoading()
41-
setFeedback('error')
42-
})
43-
}
12+
function InviteUser({ oid }: { oid?: TOrganizationID }) {
13+
const [formState, formAction] = useFormState(inviteUserAction, undefined)
4414

4515
return (
46-
<form className={classes.inviteForm} onSubmit={submitHandler}>
16+
<form className={classes.inviteForm} action={formAction}>
17+
<HiddenInput id="oid" value={oid} />
4718
<div className="flexColumn g-1 w-100">
4819
<TextField
4920
name="email"
5021
label="E-post"
5122
type="email"
52-
{...getTextFieldProps()}
23+
{...getFormStateProps(formState)}
5324
/>
5425
</div>
55-
<Button
26+
<SubmitButton
5627
variant="primary"
57-
loading={isLoading}
58-
onClick={clearFeedback}
5928
width="fluid"
6029
className={classes.addMemberButton}
6130
>
6231
Legg til medlem
6332
<AddIcon />
64-
</Button>
33+
</SubmitButton>
6534
</form>
6635
)
6736
}

0 commit comments

Comments
 (0)