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(landing): contact form #1636

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
12 changes: 11 additions & 1 deletion tavla/app/(admin)/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function getFormFeedbackForField(
export function getFormFeedbackForError(
e?: TError,
email?: string,
): TFormFeedback {
): TFormFeedback | undefined {
let code = e
if (e instanceof FirebaseError) {
code = e.code
Expand Down Expand Up @@ -234,6 +234,16 @@ export function getFormFeedbackForError(
variant: 'error',
}
}
case 'contact/message-missing': {
return {
form_type: 'user',
purusott marked this conversation as resolved.
Show resolved Hide resolved
feedback: 'Vennligst legg igjen en melding.',
variant: 'error',
}
}
case 'form/reset': {
return
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synes det virker litt rart å legge til en error-handling for å resette en form. Vi burde finne en annen løsning. Jeg tittet litt rundt, kanskje vi kan legge til et ekstra felt som heter "success"? Litt som de gjør her: https://react.dev/reference/react/useActionState#display-information-after-submitting-a-form

Vi kan sette oss ned å gjøre denne sammen tenker jeg:)

}

return {
Expand Down
126 changes: 126 additions & 0 deletions tavla/app/components/ContactForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
'use client'
import { TextArea, TextField } from '@entur/form'
import { Label, Paragraph } from '@entur/typography'
import { SubmitButton } from 'components/Form/SubmitButton'
import { postForm } from './actions'
import {
TFormFeedback,
getFormFeedbackForError,
getFormFeedbackForField,
} from 'app/(admin)/utils'
import { useState } from 'react'
import { FormError } from 'app/(admin)/components/FormError'
import { useToast } from '@entur/alert'
import { Expandable } from './Expandable'
import { usePostHog } from 'posthog-js/react'
import { isEmptyOrSpaces } from 'app/(admin)/edit/utils'

function ContactForm() {
const posthog = usePostHog()

const { addToast } = useToast()
const [isOpen, setIsOpen] = useState(false)
const [formState, setFormError] = useState<TFormFeedback | undefined>(
undefined,
)

const validEmail = new RegExp(/^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$/g)

const submit = async (data: FormData) => {
const email = data.get('email') as string
const message = data.get('message') as string

if (!validEmail.test(email))
return setFormError(getFormFeedbackForError('auth/missing-email'))

if (isEmptyOrSpaces(message))
return setFormError(
getFormFeedbackForError('contact/message-missing'),
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kan man flytte all denne valideringen til server-siden?

    const validEmail = new RegExp(/^[\w.-]+@([\w-]+\.)+[\w-]{2,4}$/g)

    if (!validEmail.test(email))
        return getFormFeedbackForError('auth/missing-email')

    if (isEmptyOrSpaces(message)) 
        return getFormFeedbackForError('contact/message-missing')
    

Da tror jeg kanskje vi kan eliminere setFormError helt

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det skjer i server også, så kan i prinsippet fjerne det helt. Tenkte bare det blir dårlig UX å validere bare i server (selv om det er viktigere der) mtp nettverkskall og latency.

Et alternativ er å bruke html sin innebygde validering. Da blir feilmeldingene på engelsk med mindre vi overstyrer med userefs
Men hvis vi kun ønsker server validering kan jeg fjerne usestate greiene :)

const error = await postForm(formState, data)

if (error) return setFormError(error)
else {
setIsOpen(false)
setFormError(undefined)
addToast('Takk for tilbakemelding!')
}
}

return (
<div
className="flex items-center justify-center w-full xl:w-1/6 h-14"
onClick={() =>
isOpen
? posthog.capture('CONTACT_FORM_OPENED')
: setFormError(undefined)
}
>
<Expandable
title="Send oss en melding!"
isOpen={isOpen}
setIsOpen={setIsOpen}
>
<form
action={submit}
className="flex flex-col gap-4 p-4 sm:p-6 "
>
<Paragraph as="h1" margin="none" className="font-bold">
purusott marked this conversation as resolved.
Show resolved Hide resolved
Vi setter stor pris på tilbakemeldinger og innspill, og
bistår gjerne hvis du vil ha hjelp til å komme i gang
med Tavla!
</Paragraph>
<div>
<Label
htmlFor="email"
className="font-bold"
aria-required
>
E-post *
emilielr marked this conversation as resolved.
Show resolved Hide resolved
</Label>

<TextField
label="E-postadresse"
name="email"
purusott marked this conversation as resolved.
Show resolved Hide resolved
id="email"
aria-label="E-postadresse"
{...getFormFeedbackForField('email', formState)}
/>
</div>
<div>
<Label
htmlFor="message"
className="font-bold"
aria-required
>
Melding *
</Label>
<TextArea
name="message"
id="message"
label="Melding"
purusott marked this conversation as resolved.
Show resolved Hide resolved
aria-label="Skriv her"
aria-required
{...getFormFeedbackForField('user', formState)}
/>
</div>
SelmaBergstrand marked this conversation as resolved.
Show resolved Hide resolved
<Paragraph margin="none">
Hvis du ønsker å legge ved bilder, kan du sende en
e-post til tavla@entur.org.
</Paragraph>
<FormError
{...getFormFeedbackForField('general', formState)}
/>
<SubmitButton
variant="primary"
width="fluid"
aria-label="Send"
>
Send
</SubmitButton>
</form>
</Expandable>
</div>
)
}
export { ContactForm }
36 changes: 36 additions & 0 deletions tavla/app/components/Expandable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { IconButton } from '@entur/button'
import { DownArrowIcon, UpArrowIcon } from '@entur/icons'
import { Heading5 } from '@entur/typography'
import { Dispatch, SetStateAction } from 'react'

function Expandable({
emilielr marked this conversation as resolved.
Show resolved Hide resolved
title,
isOpen,
setIsOpen,
children,
}: {
title: string
isOpen: boolean
setIsOpen: Dispatch<SetStateAction<boolean>>
children: React.ReactNode
}) {
return (
<div className="fixed bottom-0 md:right-3 w-full lg:w-1/2 xl:w-1/3 z-10 drop-shadow-lg ">
<div
onClick={() => setIsOpen(!isOpen)}
className="flex justify-between items-center px-6 py-4 bg-blue80 w-full rounded-t"
>
<Heading5 margin="none" className=" sm:text-base !text-lg">
{title}
</Heading5>
<IconButton className="border-0!">
{isOpen ? <DownArrowIcon /> : <UpArrowIcon />}
</IconButton>
</div>
{isOpen && (
<div className="rounded-b p-4 bg-blue90">{children}</div>
)}
</div>
)
}
export { Expandable }
24 changes: 0 additions & 24 deletions tavla/app/components/FloatingContact.tsx

This file was deleted.

109 changes: 109 additions & 0 deletions tavla/app/components/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use server'

import { TFormFeedback, getFormFeedbackForError } from 'app/(admin)/utils'

async function postForm(prevState: TFormFeedback | undefined, data: FormData) {
const email = data?.get('email') as string
const message = data?.get('message') as string

if (!email) return getFormFeedbackForError('auth/missing-email')

if (!message) return getFormFeedbackForError('contact/message-missing')

const timestamp = Math.floor(Date.now() / 1000)

const payload = {
purusott marked this conversation as resolved.
Show resolved Hide resolved
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: ':email: Ny melding :email:',
emoji: true,
},
},
{
type: 'divider',
},
{
type: 'rich_text',
elements: [
{
type: 'rich_text_section',
elements: [
{
type: 'date',
timestamp: timestamp,
format: '{date_num} klokken {time}',
fallback: 'timey',
},
],
},
],
},
{
type: 'section',
block_id: 'email',
fields: [
{
type: 'mrkdwn',
text: `*Fra:* \n${email}`,
},
],
},

{
type: 'rich_text',
elements: [
{
type: 'rich_text_section',
elements: [
{
type: 'text',
text: 'Melding:',
style: {
bold: true,
},
},
],
},
],
},
{
type: 'rich_text',
elements: [
{
type: 'rich_text_preformatted',
elements: [
{
type: 'text',
text: `${message}`,
},
],
},
],
},
],
}

try {
const url = process.env.SLACK_WEBHOOK_URL
if (!url) throw Error('Could not find url')
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})

if (!response.ok) {
throw Error('Error in request')
}
return getFormFeedbackForError('form/reset')
} catch (e: unknown) {
return getFormFeedbackForError('general')
}
SelmaBergstrand marked this conversation as resolved.
Show resolved Hide resolved
}

export { postForm }
4 changes: 2 additions & 2 deletions tavla/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { Metadata } from 'next'
import dynamic from 'next/dynamic'
import { EnturToastProvider, PHProvider } from './providers'
import { Footer } from './(admin)/components/Footer'
import { FloatingContact } from './components/FloatingContact'
import { TopNavigation } from './(admin)/components/TopNavigation'
import { cookies } from 'next/headers'
import { verifySession } from './(admin)/utils/firebase'
import { ContactForm } from './components/ContactForm'

export const metadata: Metadata = {
title: 'Entur Tavla',
Expand Down Expand Up @@ -52,7 +52,7 @@ async function RootLayout({ children }: { children: ReactNode }) {
<TopNavigation loggedIn={loggedIn} />
<PostHogPageView />
{children}
<FloatingContact />
purusott marked this conversation as resolved.
Show resolved Hide resolved
<ContactForm />
<Footer />
</body>
</EnturToastProvider>
Expand Down
7 changes: 7 additions & 0 deletions tavla/helm/tavla/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ common:
secretKeyRef:
name: backend
key: api-key

- name: SLACK_WEBHOOK_URL
valueFrom:
secretKeyRef:
name: slack-webhook
key: url

probes:
enabled: true
spec:
Expand Down