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

- @@ -88,15 +106,24 @@ export default function LanguagesPage() { title="No languages yet" description="Languages define the translation targets for your projects. Create one to get started." actionLabel="Create Language" - onAction={openDialog} + onAction={openCreateDialog} /> ) : (
{languages.map((lang) => (
+
{lang.code} @@ -116,9 +143,11 @@ export default function LanguagesPage() { - Create Language + {editingLang ? "Edit Language" : "Create Language"} - Add a new target language for your translation projects. + {editingLang + ? "Update the language name or code." + : "Add a new target language for your translation projects."}
@@ -159,10 +188,16 @@ export default function LanguagesPage() { Cancel diff --git a/src/services/api.ts b/src/services/api.ts index d0f3a8c..4b88a9f 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -16,6 +16,7 @@ import type { AppRoleResponse, LanguageResponse, LanguageCreate, + LanguageUpdate, OrganizationResponse, OrganizationCreate, OrganizationUpdate, @@ -170,6 +171,8 @@ export const languagesAPI = { list: () => api.get("/languages"), create: (data: LanguageCreate) => api.post("/languages", data), + update: (languageId: string, data: LanguageUpdate) => + api.put(`/languages/${languageId}`, data), get: (languageId: string) => api.get(`/languages/${languageId}`), getByCode: (code: string) => diff --git a/src/types/index.ts b/src/types/index.ts index e343aa0..a4103a6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -25,6 +25,7 @@ export type { export type { LanguageResponse, LanguageCreate, + LanguageUpdate, } from "./language" export type { diff --git a/src/types/language.ts b/src/types/language.ts index fb5a8bd..7fcf6d6 100644 --- a/src/types/language.ts +++ b/src/types/language.ts @@ -3,9 +3,15 @@ export interface LanguageResponse { name: string code: string created_at: string + updated_at: string } export interface LanguageCreate { name: string code: string } + +export interface LanguageUpdate { + name?: string + code?: string +} From 6e69ffc4ce79049979ffd7759a791eb5b4e7cc95 Mon Sep 17 00:00:00 2001 From: Levi Gomes Date: Thu, 9 Jul 2026 14:59:34 -0300 Subject: [PATCH 2/3] feat(languages): gate the edit affordance to platform admins Show the language edit pencil only when the user is a platform admin. Direct edit is admin-only per the 2026-07 rule change; managers submit an edit request (change-requests feature) rather than editing directly. --- src/components/pages/LanguagesPage.tsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/pages/LanguagesPage.tsx b/src/components/pages/LanguagesPage.tsx index 5d0d841..2ff1c83 100644 --- a/src/components/pages/LanguagesPage.tsx +++ b/src/components/pages/LanguagesPage.tsx @@ -18,10 +18,12 @@ import { import { LoadingSpinner } from "@/components/common/LoadingSpinner" import { EmptyState } from "@/components/common/EmptyState" import { InfoTooltip } from "@/components/common/InfoTooltip" +import { useAuth } from "@/contexts/AuthContext" import { formatDate } from "@/utils/format" export default function LanguagesPage() { + const { isPlatformAdmin } = useAuth() const { languages, loading: storeLoading, lastFetched, fetch: fetchLanguages } = useLanguagesStore() const loading = storeLoading || (!lastFetched && languages.length === 0) const [dialogOpen, setDialogOpen] = useState(false) @@ -115,15 +117,17 @@ export default function LanguagesPage() { key={lang.id} className="group relative rounded-2xl border border-areia/20 bg-surface p-5 shadow-sm hover:shadow-md hover:border-telha/30 transition-all duration-200 cursor-default" > - + {isPlatformAdmin && ( + + )}
{lang.code} From d682b2e9204f3b98f5ff0ab34a8a6ab03d584db0 Mon Sep 17 00:00:00 2001 From: Levi Gomes Date: Wed, 15 Jul 2026 17:01:21 -0300 Subject: [PATCH 3/3] chore(ci): empty commit to re-trigger CI checks Co-Authored-By: Claude Opus 4.8 (1M context)