208 lines
5.3 KiB
TypeScript
208 lines
5.3 KiB
TypeScript
import { FormEvent, useEffect, useRef, useState } from 'react'
|
|
import TaskBoard from './TaskBoard'
|
|
|
|
type User = {
|
|
id: string
|
|
name: string
|
|
createdAt: string
|
|
}
|
|
|
|
type ApiError = {
|
|
code?: string
|
|
message?: string
|
|
}
|
|
|
|
const ACTIVE_USER_KEY = 'hemhub.activeUserId'
|
|
|
|
function App() {
|
|
const [users, setUsers] = useState<User[]>([])
|
|
const [activeUser, setActiveUser] = useState<User | null>(null)
|
|
const [loadState, setLoadState] = useState<'loading' | 'ready' | 'error'>('loading')
|
|
const [showCreateUser, setShowCreateUser] = useState(false)
|
|
|
|
const loadUsers = async () => {
|
|
setLoadState('loading')
|
|
|
|
try {
|
|
const response = await fetch('/api/users')
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Kunde inte hämta användare')
|
|
}
|
|
|
|
const loadedUsers = (await response.json()) as User[]
|
|
const storedUserId = window.localStorage.getItem(ACTIVE_USER_KEY)
|
|
const storedUser = loadedUsers.find((user) => user.id === storedUserId) ?? null
|
|
|
|
if (storedUserId && !storedUser) {
|
|
window.localStorage.removeItem(ACTIVE_USER_KEY)
|
|
}
|
|
|
|
setUsers(loadedUsers)
|
|
setActiveUser(storedUser)
|
|
setShowCreateUser(loadedUsers.length === 0)
|
|
setLoadState('ready')
|
|
} catch {
|
|
setLoadState('error')
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
void loadUsers()
|
|
}, [])
|
|
|
|
const selectUser = (user: User) => {
|
|
window.localStorage.setItem(ACTIVE_USER_KEY, user.id)
|
|
setActiveUser(user)
|
|
}
|
|
|
|
const logOut = () => {
|
|
window.localStorage.removeItem(ACTIVE_USER_KEY)
|
|
setActiveUser(null)
|
|
setShowCreateUser(false)
|
|
}
|
|
|
|
if (loadState === 'loading') {
|
|
return <main className="panel">Laddar HemHub…</main>
|
|
}
|
|
|
|
if (loadState === 'error') {
|
|
return (
|
|
<main className="panel">
|
|
<h1>HemHub</h1>
|
|
<p>Kunde inte ansluta till HemHub.</p>
|
|
<button type="button" onClick={() => void loadUsers()}>
|
|
Försök igen
|
|
</button>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (activeUser) {
|
|
return <TaskBoard activeUserName={activeUser.name} users={users} onLogOut={logOut} />
|
|
}
|
|
|
|
if (showCreateUser) {
|
|
return (
|
|
<CreateUserForm
|
|
hasExistingUsers={users.length > 0}
|
|
onCancel={() => setShowCreateUser(false)}
|
|
onCreated={(user) => {
|
|
setUsers((currentUsers) => [...currentUsers, user])
|
|
selectUser(user)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<main className="panel">
|
|
<h1>HemHub</h1>
|
|
<h2>Vem är du?</h2>
|
|
<div className="user-list">
|
|
{users.map((user) => (
|
|
<button type="button" key={user.id} onClick={() => selectUser(user)}>
|
|
{user.name}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<button type="button" className="secondary" onClick={() => setShowCreateUser(true)}>
|
|
Skapa användare
|
|
</button>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
type CreateUserFormProps = {
|
|
hasExistingUsers: boolean
|
|
onCancel: () => void
|
|
onCreated: (user: User) => void
|
|
}
|
|
|
|
function CreateUserForm({ hasExistingUsers, onCancel, onCreated }: CreateUserFormProps) {
|
|
const [name, setName] = useState('')
|
|
const [error, setError] = useState('')
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const isSubmittingRef = useRef(false)
|
|
|
|
const submit = async (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault()
|
|
|
|
if (isSubmittingRef.current) {
|
|
return
|
|
}
|
|
|
|
const trimmedName = name.trim()
|
|
|
|
if (!trimmedName || [...trimmedName].length > 50) {
|
|
setError('Namnet måste innehålla mellan 1 och 50 tecken.')
|
|
return
|
|
}
|
|
|
|
setError('')
|
|
isSubmittingRef.current = true
|
|
setIsSubmitting(true)
|
|
|
|
try {
|
|
const response = await fetch('/api/users', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ name: trimmedName }),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const apiError = (await response.json().catch(() => ({}))) as ApiError
|
|
|
|
if (
|
|
apiError.code === 'INVALID_USER_NAME' ||
|
|
apiError.code === 'USER_NAME_ALREADY_EXISTS'
|
|
) {
|
|
setError(apiError.message ?? 'Det gick inte att skapa användaren.')
|
|
} else {
|
|
setError('Det gick inte att skapa användaren. Försök igen.')
|
|
}
|
|
return
|
|
}
|
|
|
|
const createdUser = (await response.json()) as User
|
|
onCreated(createdUser)
|
|
} catch {
|
|
setError('Det gick inte att skapa användaren. Försök igen.')
|
|
} finally {
|
|
isSubmittingRef.current = false
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<main className="panel">
|
|
<h1>HemHub</h1>
|
|
<h2>Skapa användare</h2>
|
|
<form onSubmit={(event) => void submit(event)}>
|
|
<label htmlFor="user-name">Namn</label>
|
|
<input
|
|
id="user-name"
|
|
value={name}
|
|
disabled={isSubmitting}
|
|
onChange={(event) => setName(event.target.value)}
|
|
/>
|
|
{error && (
|
|
<p className="error" role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
<button type="submit" disabled={isSubmitting}>
|
|
{isSubmitting ? 'Skapar…' : 'Skapa användare'}
|
|
</button>
|
|
{hasExistingUsers && (
|
|
<button type="button" className="secondary" disabled={isSubmitting} onClick={onCancel}>
|
|
Tillbaka
|
|
</button>
|
|
)}
|
|
</form>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default App
|