From 236b9afb7536b88c3d24de3bfb68c4860b7e52fc Mon Sep 17 00:00:00 2001
From: levig
Date: Sun, 5 Jul 2026 01:28:59 -0300
Subject: [PATCH 1/3] feat(languages): edit a language's name and code in a
dialog
Add an edit affordance (pencil on hover) to each language card that opens the
shared dialog pre-filled with the language's name and code. Saving calls the new
languagesAPI.update (PUT), refreshes the store, and surfaces duplicate-code (409)
errors. Add the LanguageUpdate type and updated_at to LanguageResponse (US-1.1).
---
src/components/pages/LanguagesPage.tsx | 61 ++++++++++++++++++++------
src/services/api.ts | 3 ++
src/types/index.ts | 1 +
src/types/language.ts | 6 +++
4 files changed, 58 insertions(+), 13 deletions(-)
diff --git a/src/components/pages/LanguagesPage.tsx b/src/components/pages/LanguagesPage.tsx
index da49e1b..5d0d841 100644
--- a/src/components/pages/LanguagesPage.tsx
+++ b/src/components/pages/LanguagesPage.tsx
@@ -1,7 +1,8 @@
import { useEffect, useState } from "react"
-import { Languages, Plus } from "lucide-react"
+import { Languages, Plus, Pencil } from "lucide-react"
import { toast } from "sonner"
import { languagesAPI } from "@/services/api"
+import type { LanguageResponse } from "@/types"
import { useLanguagesStore } from "@/stores/languagesStore"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
@@ -25,6 +26,7 @@ export default function LanguagesPage() {
const loading = storeLoading || (!lastFetched && languages.length === 0)
const [dialogOpen, setDialogOpen] = useState(false)
const [creating, setCreating] = useState(false)
+ const [editingLang, setEditingLang] = useState(null)
const [name, setName] = useState("")
const [code, setCode] = useState("")
@@ -33,18 +35,34 @@ export default function LanguagesPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
- function openDialog() {
+ function openCreateDialog() {
+ setEditingLang(null)
setName("")
setCode("")
setDialogOpen(true)
}
- async function handleCreate() {
+ function openEditDialog(lang: LanguageResponse) {
+ setEditingLang(lang)
+ setName(lang.name)
+ setCode(lang.code)
+ setDialogOpen(true)
+ }
+
+ async function handleSave() {
if (!name.trim() || code.trim().length !== 3) return
setCreating(true)
try {
- await languagesAPI.create({ name: name.trim(), code: code.trim().toLowerCase() })
- toast.success("Language created")
+ if (editingLang) {
+ await languagesAPI.update(editingLang.id, {
+ name: name.trim(),
+ code: code.trim().toLowerCase(),
+ })
+ toast.success("Language updated")
+ } else {
+ await languagesAPI.create({ name: name.trim(), code: code.trim().toLowerCase() })
+ toast.success("Language created")
+ }
setDialogOpen(false)
useLanguagesStore.getState().invalidate()
await fetchLanguages()
@@ -53,7 +71,7 @@ export default function LanguagesPage() {
if (status === 409) {
toast.error("A language with this code already exists")
} else {
- toast.error("Failed to create language")
+ toast.error(editingLang ? "Failed to update language" : "Failed to create language")
}
} finally {
setCreating(false)
@@ -76,7 +94,7 @@ export default function LanguagesPage() {
{languages.length} language{languages.length !== 1 ? "s" : ""} registered
-