Skip to content

Commit

Permalink
Merge pull request #25 from espoon-voltti/updates-to-basic-flow
Browse files Browse the repository at this point in the history
updates to basic flow
  • Loading branch information
Joosakur authored Nov 24, 2023
2 parents c16c7c8 + 7bd68c8 commit ec44437
Show file tree
Hide file tree
Showing 22 changed files with 961 additions and 539 deletions.
69 changes: 46 additions & 23 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React from 'react'
import { Navigate, createBrowserRouter, Outlet, Link } from 'react-router-dom'
import { Navigate, createBrowserRouter, Outlet } from 'react-router-dom'
import styled from 'styled-components'

import { AuthGuard } from './auth/AuthGuard'
import { UserContextProvider } from './auth/UserContext'
import { UserHeader } from './auth/UserHeader'
import { H1 } from './shared/typography'
import { CreateStudentPage } from './students/CreateStudentPage'
import { StudentCasesSearchPage } from './students/StudentCasesSearchPage'
import { LoginPage } from './students/LoginPage'
import { StudentPage } from './students/StudentPage'
import { StudentsSearchPage } from './students/StudentsSearchPage'

Expand All @@ -16,27 +20,30 @@ const AppContainer = styled.div`
`

const Header = styled.nav`
height: 64px;
height: 80px;
display: flex;
flex-direction: row;
justify-content: flex-start;
justify-content: space-between;
align-items: center;
border-bottom: 2px double #888;
margin-bottom: 16px;
> * {
margin-right: 32px;
}
margin-bottom: 32px;
padding: 0 32px;
background-color: #fff;
`

function App() {
return (
<AppContainer>
<Header>
<Link to="/tapaukset">Tapaukset</Link>
<Link to="/oppivelvolliset">Oppivelvolliset</Link>
</Header>
<Outlet />
</AppContainer>
<UserContextProvider>
<div>
<Header>
<H1>Espoon kaupunki - Oppivelvollisuuden valvonta</H1>
<UserHeader />
</Header>
<AppContainer>
<Outlet />
</AppContainer>
</div>
</UserContextProvider>
)
}

Expand All @@ -46,28 +53,44 @@ export const appRouter = createBrowserRouter([
element: <App />,
children: [
{
path: '/oppivelvolliset',
element: <StudentsSearchPage />
path: '/kirjaudu',
element: (
<AuthGuard allow="UNAUTHENTICATED_ONLY">
<LoginPage />
</AuthGuard>
)
},
{
path: '/tapaukset',
element: <StudentCasesSearchPage />
path: '/oppivelvolliset',
element: (
<AuthGuard allow="AUTHENTICATED_ONLY">
<StudentsSearchPage />
</AuthGuard>
)
},
{
path: '/oppivelvolliset/uusi',
element: <CreateStudentPage />
element: (
<AuthGuard allow="AUTHENTICATED_ONLY">
<CreateStudentPage />
</AuthGuard>
)
},
{
path: '/oppivelvolliset/:id',
element: <StudentPage />
element: (
<AuthGuard allow="AUTHENTICATED_ONLY">
<StudentPage />
</AuthGuard>
)
},
{
path: '/*',
element: <Navigate replace to="/tapaukset" />
element: <Navigate replace to="/oppivelvolliset" />
},
{
index: true,
element: <Navigate replace to="/tapaukset" />
element: <Navigate replace to="/oppivelvolliset" />
}
]
}
Expand Down
33 changes: 33 additions & 0 deletions frontend/src/auth/AuthGuard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useContext } from 'react'
import { Navigate } from 'react-router-dom'

import { UserContext } from './UserContext'

type AuthMode = 'AUTHENTICATED_ONLY' | 'UNAUTHENTICATED_ONLY' | 'ALL'

export const AuthGuard = React.memo(function AuthGuard({
allow,
children
}: {
allow: AuthMode
children: React.JSX.Element
}) {
const { user } = useContext(UserContext)

switch (allow) {
case 'ALL':
return children
case 'AUTHENTICATED_ONLY':
if (user) {
return children
} else {
return <Navigate replace to="/kirjaudu" />
}
case 'UNAUTHENTICATED_ONLY':
if (user) {
return <Navigate replace to="/oppivelvolliset" />
} else {
return children
}
}
})
40 changes: 40 additions & 0 deletions frontend/src/auth/UserContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { createContext, useMemo, useState } from 'react'

export interface User {
name: string
}

export interface UserState {
user: User | null
setLoggedIn: (user: User) => void
setLoggedOut: () => void
}

export const UserContext = createContext<UserState>({
user: {
name: 'Tessa Testaaja'
},
setLoggedIn: () => undefined,
setLoggedOut: () => undefined
})

export const UserContextProvider = React.memo(function UserContextProvider({
children
}: {
children: React.JSX.Element
}) {
const [user, setUser] = useState<User | null>({
name: 'Tessa Testaaja'
})

const value = useMemo(
() => ({
user,
setLoggedIn: setUser,
setLoggedOut: () => setUser(null)
}),
[user, setUser]
)

return <UserContext.Provider value={value}>{children}</UserContext.Provider>
})
20 changes: 20 additions & 0 deletions frontend/src/auth/UserHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useContext } from 'react'

import { FlexRowWithGaps } from '../shared/layout'

import { UserContext } from './UserContext'

export const UserHeader = React.memo(function UserHeader() {
const { user, setLoggedOut } = useContext(UserContext)

if (!user) return null

return (
<FlexRowWithGaps>
<span>{user.name}</span>
<a href="#" onClick={setLoggedOut}>
Kirjaudu ulos
</a>
</FlexRowWithGaps>
)
})
13 changes: 13 additions & 0 deletions frontend/src/shared/api-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type JsonOf<T> = T extends string | number | boolean | null | undefined
? T
: T extends Date
? string
: T extends Map<string, infer U>
? { [key: string]: JsonOf<U> }
: T extends Set<infer U>
? JsonOf<U>[]
: T extends (infer U)[]
? JsonOf<U>[]
: T extends object // eslint-disable-line @typescript-eslint/ban-types
? { [P in keyof T]: JsonOf<T[P]> }
: never
5 changes: 4 additions & 1 deletion frontend/src/shared/dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { format, parse } from 'date-fns'

export const parseDate = (date: string) => {
try {
return parse(date, 'dd.MM.yyyy', new Date())
const parsed = parse(date, 'dd.MM.yyyy', new Date())
if (Number.isNaN(parsed.valueOf())) return undefined

return parsed
} catch (e) {
return undefined
}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/shared/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const FlexCol = styled.div`
export const FlexRow = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`

export const FlexColWithGaps = styled(FlexCol)<{ $gapSize?: 's' | 'm' | 'L' }>`
Expand All @@ -21,6 +22,10 @@ export const FlexRowWithGaps = styled(FlexRow)<{ $gapSize?: 's' | 'm' | 'L' }>`
> * {
margin-right: ${(p) =>
p.$gapSize === 'L' ? '32px' : p.$gapSize === 'm' ? '16px' : '8px'};
&:last-child {
margin-right: 0;
}
}
`

Expand All @@ -38,6 +43,7 @@ export const Table = styled.table`
td {
border-top: 1px solid #888;
border-right: 1px solid #888;
padding: 8px;
}
td:last-child {
Expand Down
57 changes: 26 additions & 31 deletions frontend/src/students/CreateStudentPage.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,48 @@
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Link, useNavigate } from 'react-router-dom'

import { FlexColWithGaps, VerticalGap } from '../shared/layout'
import { H1, Label } from '../shared/typography'
import { VerticalGap } from '../shared/layout'
import { H2, H3 } from '../shared/typography'

import { apiPostStudent } from './api'
import { StudentCaseForm } from './StudentCaseForm'
import { StudentForm } from './StudentForm'
import { apiPostStudent, StudentCaseInput, StudentInput } from './api'

export const CreateStudentPage = React.memo(function CreateStudentPage() {
const navigate = useNavigate()
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [submitting, setSubmitting] = useState(false)

const valid = firstName.trim() && lastName.trim()
const [studentInput, setStudentInput] = useState<StudentInput | null>(null)
const [studentCaseInput, setStudentCaseInput] =
useState<StudentCaseInput | null>(null)
const [submitting, setSubmitting] = useState(false)

return (
<div>
<H1>Uusi oppivelvollinen</H1>

<Link to="/oppivelvolliset">Takaisin</Link>
<VerticalGap $size="L" />

<FlexColWithGaps $gapSize="m">
<FlexColWithGaps>
<Label>Etunimi</Label>
<input
type="text"
onChange={(e) => setFirstName(e.target.value)}
value={firstName}
/>
</FlexColWithGaps>
<FlexColWithGaps>
<Label>Sukunimi</Label>
<input
type="text"
onChange={(e) => setLastName(e.target.value)}
value={lastName}
/>
</FlexColWithGaps>
</FlexColWithGaps>
<H2>Uusi oppivelvollinen</H2>

<VerticalGap $size="L" />

<H3>Oppivelvollisen tiedot</H3>
<VerticalGap $size="m" />
<StudentForm onChange={setStudentInput} />
<VerticalGap $size="m" />
<H3>Ilmoituksen tiedot</H3>
<VerticalGap $size="m" />
<StudentCaseForm onChange={setStudentCaseInput} />
<VerticalGap $size="m" />

<button
disabled={submitting || !valid}
disabled={submitting || !studentInput || !studentCaseInput}
onClick={() => {
if (!studentInput || !studentCaseInput) return

setSubmitting(true)
apiPostStudent({
firstName: firstName.trim(),
lastName: lastName.trim()
student: studentInput,
studentCase: studentCaseInput
})
.then((id) => navigate(`/oppivelvolliset/${id}`))
.catch(() => setSubmitting(false))
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/students/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useContext } from 'react'
import styled from 'styled-components'

import { UserContext } from '../auth/UserContext'
import { FlexColWithGaps } from '../shared/layout'
import { H2 } from '../shared/typography'

const Wrapper = styled.div`
width: 100%;
height: 600px;
display: flex;
align-items: center;
justify-content: center;
`

export const LoginPage = React.memo(function LoginPage() {
const { setLoggedIn } = useContext(UserContext)

return (
<Wrapper>
<FlexColWithGaps $gapSize="L">
<H2>Kirjaudu sisään Espoo-AD:lla</H2>
<button onClick={() => setLoggedIn({ name: 'Tessa Testaaja' })}>
Kirjaudu sisään
</button>
</FlexColWithGaps>
</Wrapper>
)
})
Loading

0 comments on commit ec44437

Please sign in to comment.