Merge pull request 'furqan' (#15) from furqan into dev

Reviewed-on: https://gitea.maskantech.in/gitea_admin/saas_frontend/pulls/15
This commit is contained in:
furqan
2026-02-02 17:40:38 +00:00
22 changed files with 268 additions and 91 deletions
@@ -21,7 +21,6 @@ export default function SignUpForm() {
setIsLoading(true);
if (!isChecked) {
// Require terms acceptance before sending the signup request.
setErrorMessage("Please accept the terms and conditions to continue.");
setIsLoading(false);
return;
@@ -35,10 +34,8 @@ export default function SignUpForm() {
};
try {
// Call the backend signup API.
await authApi.signup(payload);
// Signup does not return tokens, so take the user to signin.
navigate("/signin");
} catch (error) {
const message =
+11 -9
View File
@@ -3,9 +3,11 @@ import axios from 'axios';
import { ExternalLink, Box, Search, Rocket } from 'lucide-react';
import { moduleApi, type Module } from '../modules/user/UserModuleApi';
import { useAuth } from '../../context/AuthContext';
import { useTranslation } from 'react-i18next';
const Dashboard = () => {
const { user } = useAuth();
const { t } = useTranslation(['dashboard', 'common']);
const [modules, setModules] = useState<Module[]>([]);
const [loading, setLoading] = useState(true);
@@ -54,7 +56,7 @@ const Dashboard = () => {
if (filtered.length === 0) return {};
filtered.forEach(module => {
const category = module.category || 'All Modules';
const category = module.category || t('dashboard:allModules');
if (!groups[category]) {
groups[category] = [];
}
@@ -68,7 +70,7 @@ const Dashboard = () => {
},
{} as Record<string, Module[]>
);
}, [modules, searchTerm]);
}, [modules, searchTerm, t]);
return (
<div className="p-8 space-y-10 min-h-screen bg-gray-50/50">
@@ -76,10 +78,10 @@ const Dashboard = () => {
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6 animate-fade-in-up">
<div>
<h1 className="text-4xl font-extrabold text-(--text-primary) tracking-tight mb-2">
Welcome back, {user?.first_name || 'Admin'}! 👋
{t('dashboard:title')}, {user?.first_name || 'Admin'}! 👋
</h1>
<p className="text-lg text-(--text-secondary)">
Manage your SaaS ecosystem from one central hub.
{t('dashboard:subtitle')}
</p>
</div>
@@ -90,7 +92,7 @@ const Dashboard = () => {
</div>
<input
type="text"
placeholder="Search available modules..."
placeholder={t('dashboard:searchPlaceholder')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="block w-full pl-10 pr-3 py-2.5 bg-white border border-gray-200 rounded-xl leading-5 text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all shadow-sm hover:shadow-md"
@@ -160,7 +162,7 @@ const Dashboard = () => {
className="w-full flex items-center justify-center gap-2 py-2.5 px-4 bg-gray-50 hover:bg-blue-600 text-gray-700 hover:text-white rounded-xl font-medium transition-all duration-200 group-hover:shadow-md"
>
<Rocket size={18} className="transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" />
Launch Module
{t('dashboard:actions.launch')}
</button>
</div>
</div>
@@ -173,16 +175,16 @@ const Dashboard = () => {
<div className="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mb-4">
<Box className="w-8 h-8 text-gray-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-1">No modules found</h3>
<h3 className="text-lg font-semibold text-gray-900 mb-1">{t('common:common.noData')}</h3>
<p className="text-gray-500 max-w-sm mx-auto">
{searchTerm ? `No results found for "${searchTerm}". Try a different keyword.` : "Your dashboard is currently empty. Contact your administrator to assign modules."}
{searchTerm ? t('common:common.noData') : "Your dashboard is currently empty. Contact your administrator to assign modules."}
</p>
{searchTerm && (
<button
onClick={() => setSearchTerm('')}
className="mt-4 text-blue-600 font-medium hover:underline"
>
Clear Search
{t('common:actions.reset')}
</button>
)}
</div>
@@ -10,7 +10,7 @@ import CustomButton from '../../../../components/custom/CustomButton';
const ModuleList = () => {
const navigate = useNavigate();
const { t } = useTranslation('modules');
const { t } = useTranslation(['modules', 'common']);
const { listModules, deleteModule, loading } = useModuleApi();
const [modules, setModules] = useState<Module[]>([]);
const [showForm, setShowForm] = useState(false);
@@ -125,14 +125,14 @@ const ModuleList = () => {
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm text-(--text-primary)"
>
<Settings size={16} />
Environments
{t('registry.card.environments')}
</button>
<button
onClick={() => navigate(`/admin/modules/${module.id}/permissions`)}
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors text-sm text-(--text-primary)"
>
<Shield size={16} />
Permissions
{t('registry.card.permissions')}
</button>
</div>
@@ -142,14 +142,14 @@ const ModuleList = () => {
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-blue-50 hover:bg-blue-100 text-blue-600 rounded-lg transition-colors text-sm"
>
<Edit size={16} />
Edit
{t('common:actions.edit')}
</button>
<button
onClick={() => confirmDelete(module)}
className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-red-50 hover:bg-red-100 text-red-600 rounded-lg transition-colors text-sm"
>
<Trash2 size={16} />
Delete
{t('common:actions.delete')}
</button>
</div>
</div>
@@ -9,8 +9,10 @@ import { paletteApi } from "../PaletteApi";
import type { ColorPalette } from "../ThemeTypes";
import PaletteForm from "./PaletteForm";
import { useTheme } from "../../../context/ThemeContext";
import { useTranslation } from "react-i18next";
const AllPalettes: React.FC = () => {
const { t } = useTranslation(['theme', 'common']);
const [palettes, setPalettes] = useState<ColorPalette[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
@@ -25,7 +27,7 @@ const AllPalettes: React.FC = () => {
const data = await paletteApi.getAllPalettes();
setPalettes(data);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to fetch palettes";
const message = error instanceof Error ? error.message : t('theme:errors.fetchFailed');
setErrorMessage(message);
console.error("Failed to fetch palettes", error);
} finally {
@@ -48,10 +50,10 @@ const AllPalettes: React.FC = () => {
};
const handleDelete = async (id: string) => {
if (!window.confirm("Are you sure you want to delete this palette?")) return;
if (!window.confirm(t('theme:confirmDelete'))) return;
try {
await paletteApi.deletePalette(id);
toast.success("Palette deleted successfully");
toast.success(t('theme:success.deleted'));
fetchPalettes();
} catch (error) {
// toast handled by api client
@@ -63,10 +65,10 @@ const AllPalettes: React.FC = () => {
try {
if (editingPalette) {
await paletteApi.updatePalette(editingPalette.id, data);
toast.success("Palette updated successfully");
toast.success(t('theme:success.updated'));
} else {
await paletteApi.createPalette(data);
toast.success("Palette created successfully");
toast.success(t('theme:success.created'));
}
setIsModalOpen(false);
fetchPalettes();
@@ -81,7 +83,7 @@ const AllPalettes: React.FC = () => {
() => [
{
key: "name",
header: "Name",
header: t('theme:table.name'),
searchable: true,
render: (row) => (
<div>
@@ -94,43 +96,43 @@ const AllPalettes: React.FC = () => {
},
{
key: "is_default",
header: "Default",
header: t('theme:table.default'),
render: (row) => (
row.is_default ? (
<CustomStatus status="Default" variant="success" />
<CustomStatus status={t('common:status.default')} variant="success" />
) : null
),
},
{
key: "colors",
header: "Preview",
header: t('theme:table.preview'),
render: (row) => (
<div className="flex items-center gap-1">
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.primary }}
title="Primary"
title={t('theme:colors.primary')}
/>
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.sidebar_bg }}
title="Sidebar"
title={t('theme:colors.sidebar')}
/>
<div
className="w-6 h-6 rounded-full border border-(--card-border) shadow-sm"
style={{ backgroundColor: row.colors.header_bg }}
title="Header"
title={t('theme:colors.header')}
/>
</div>
),
},
{
key: "actions",
header: "Actions",
header: t('common:columns.actions'),
render: (row) => (
<CustomActionMenu>
<CustomActionItem onClick={() => handleEdit(row)}>
<Edit2 size={16} className="mr-2" /> Edit Palette
<Edit2 size={16} className="mr-2" /> {t('theme:actions.edit')}
</CustomActionItem>
<CustomActionItem
@@ -146,7 +148,7 @@ const AllPalettes: React.FC = () => {
}
}}
>
<Trash2 size={16} className="mr-2" /> Delete Palette
<Trash2 size={16} className="mr-2" /> {t('theme:actions.delete')}
</div>
</CustomActionItem>
</CustomActionMenu>
@@ -160,13 +162,13 @@ const AllPalettes: React.FC = () => {
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold text-(--text-primary)">Theme Palettes</h1>
<h1 className="text-2xl font-semibold text-(--text-primary)">{t('theme:title')}</h1>
{/* <p className="text-sm text-(--text-secondary) mt-1">
Manage system color themes and default palettes.
</p> */}
</div>
<CustomButton onClick={handleCreate} leftIcon={<Plus size={18} />}>
Create Palette
{t('theme:actions.create')}
</CustomButton>
</div>
@@ -1,6 +1,7 @@
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { Check } from "lucide-react";
import { useTranslation } from "react-i18next";
import CustomModal from "../../../components/custom/CustomModal";
import CustomInput from "../../../components/custom/CustomInput";
import {CustomButton} from "../../../components/custom";
@@ -15,27 +16,27 @@ interface PaletteFormProps {
}
const colorFields = [
{ label: "Primary Color", key: "primary" },
{ label: "Primary Hover", key: "primary_hover" },
{ label: "Sidebar Background", key: "sidebar_bg" },
{ label: "Sidebar Text", key: "sidebar_text" },
{ label: "Sidebar Text Muted", key: "sidebar_text_muted" },
{ label: "Sidebar Active BG", key: "sidebar_active_bg" },
{ label: "Sidebar Active Text", key: "sidebar_active_text" },
{ label: "Sidebar Hover BG", key: "sidebar_hover_bg" },
{ label: "Sidebar Border", key: "sidebar_border" },
{ label: "Header Background", key: "header_bg" },
{ label: "Header Text", key: "header_text" },
{ label: "Header Border", key: "header_border" },
{ label: "App Background", key: "background" },
{ label: "App Secondary BG", key: "background_secondary" },
{ label: "Text Primary", key: "text_primary" },
{ label: "Text Secondary", key: "text_secondary" },
{ label: "Card Background", key: "card_bg" },
{ label: "Card Border", key: "card_border" },
{ label: "Table Header BG", key: "table_header_bg" },
{ label: "Table Row Hover", key: "table_row_hover" },
{ label: "Table Border", key: "table_border" },
{ key: "primary" },
{ key: "primary_hover" },
{ key: "sidebar_bg" },
{ key: "sidebar_text" },
{ key: "sidebar_text_muted" },
{ key: "sidebar_active_bg" },
{ key: "sidebar_active_text" },
{ key: "sidebar_hover_bg" },
{ key: "sidebar_border" },
{ key: "header_bg" },
{ key: "header_text" },
{ key: "header_border" },
{ key: "background" },
{ key: "background_secondary" },
{ key: "text_primary" },
{ key: "text_secondary" },
{ key: "card_bg" },
{ key: "card_border" },
{ key: "table_header_bg" },
{ key: "table_row_hover" },
{ key: "table_border" },
];
const emptyColors = colorFields.reduce((acc, field) => {
@@ -50,6 +51,7 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
isLoading,
isOpen,
}) => {
const { t } = useTranslation(['theme', 'common']);
const {
register,
handleSubmit,
@@ -96,7 +98,7 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
onClick={onCancel}
disabled={isLoading}
>
Cancel
{t('theme:actions.cancel')}
</CustomButton>
<CustomButton
type="submit"
@@ -105,7 +107,7 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
loading={isLoading}
leftIcon={<Check size={16} />}
>
{initialData ? "Update Palette" : "Create Palette"}
{initialData ? t('theme:actions.update') : t('theme:actions.create')}
</CustomButton>
</div>
);
@@ -114,7 +116,7 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
<CustomModal
isOpen={isOpen}
onClose={onCancel}
title={initialData ? "Edit Palette" : "Create New Palette"}
title={initialData ? t('theme:form.title.edit') : t('theme:form.title.create')}
size="lg"
footer={footer}
>
@@ -126,15 +128,15 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<CustomInput
label="Palette Name"
{...register("name", { required: "Name is required" })}
label={t('theme:form.fields.name.label')}
{...register("name", { required: t('theme:form.fields.name.required') })}
error={errors.name?.message as string}
placeholder="e.g. Dark Mode"
placeholder={t('theme:form.fields.name.placeholder')}
/>
<CustomInput
label="Description"
label={t('theme:form.fields.description.label')}
{...register("description")}
placeholder="Optional description"
placeholder={t('theme:form.fields.description.placeholder')}
/>
</div>
@@ -146,13 +148,13 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="is_default" className="text-sm font-medium text-gray-700">
Set as Default Palette
{t('theme:form.fields.isDefault')}
</label>
</div>
<div className="border-t border-gray-200 pt-4">
<h3 className="text-lg font-medium text-gray-900 mb-4">
Color Configuration
{t('theme:form.fields.sectionTitle')}
</h3>
<div className="max-h-[60vh] overflow-y-auto pr-2 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@@ -175,9 +177,9 @@ const PaletteForm: React.FC<PaletteFormProps> = ({
/>
<CustomInput
label={field.label}
label={t(`theme:colors.${field.key}`)}
{...register(`colors.${field.key}` as any, {
required: "Required",
required: t('theme:form.fields.required'),
})}
placeholder="#000000"
containerClassName="flex-1 mb-0"
+3 -3
View File
@@ -123,7 +123,7 @@ const AppHeader: React.FC = () => {
</div>
{/* RIGHT: Actions */}
<div className="flex items-center gap-2 sm:gap-4 ml-auto">
<div className="flex items-center gap-2 sm:gap-4 ms-auto">
{/* User Menu */}
<div className="relative" ref={userMenuRef}>
@@ -144,7 +144,7 @@ const AppHeader: React.FC = () => {
{initials}
</div>
<div className="hidden md:block text-left mr-1">
<div className="hidden md:block text-left me-1">
<p className="text-sm font-medium text-(--header-text) leading-none">{fullName}</p>
</div>
@@ -159,7 +159,7 @@ const AppHeader: React.FC = () => {
{isUserMenuOpen && (
<div
className="
absolute right-0 mt-2 w-56
absolute end-0 mt-2 w-56
rounded-xl border border-(--card-border)
bg-(--card-bg)
shadow-md
+11 -6
View File
@@ -83,7 +83,7 @@ const navItems: NavItem[] = [
];
const AppSidebar: React.FC = () => {
const { t } = useTranslation('common');
const { t } = useTranslation(['common', 'modules', 'dashboard']);
const navigate = useNavigate();
const {
isExpanded,
@@ -114,12 +114,17 @@ const AppSidebar: React.FC = () => {
};
const key = keyMap[name];
if (!key) {
console.warn(`Missing translation key for: ${name}`);
return name;
if (key) {
return t(`nav.${key}`, name);
}
return t(`nav.${key}`, name);
// Try other namespaces if not in nav map
return t([
`modules:${name}`,
`dashboard:${name}`,
`common:nav.${name}`,
name
]);
};
+5
View File
@@ -15,6 +15,9 @@ import arDashboard from './locales/ar/dashboard.json';
import enModules from './locales/en/modules.json';
import arModules from './locales/ar/modules.json';
import enTheme from './locales/en/theme.json';
import arTheme from './locales/ar/theme.json';
export const languages = {
en: { name: 'English', dir: 'ltr' },
ar: { name: 'العربية', dir: 'rtl' },
@@ -30,6 +33,7 @@ const resources = {
profile: enProfile,
dashboard: enDashboard,
modules: enModules,
theme: enTheme,
},
ar: {
common: arCommon,
@@ -38,6 +42,7 @@ const resources = {
profile: arProfile,
dashboard: arDashboard,
modules: arModules,
theme: arTheme,
},
};
+2 -1
View File
@@ -11,7 +11,8 @@
"profile": "الملف الشخصي",
"settings": "الإعدادات",
"configuration": "التكوين",
"dropdowns": "القوائم المنسدلة"
"dropdowns": "القوائم المنسدلة",
"modules": "الوحدات"
},
"actions": {
"save": "حفظ",
+7 -2
View File
@@ -1,4 +1,9 @@
{
"title": "لوحة المعلومات قيد التطوير",
"message": "نحن نعمل على إنشاء تجربة لوحة معلومات قوية لمنحك رؤى أفضل. يرجى التحقق مرة أخرى قريبا!"
"title": "مرحباً بعودتك",
"subtitle": "أدِر نظام SaaS الخاص بك من مركز واحد.",
"allModules": "جميع الوحدات",
"searchPlaceholder": "البحث في الوحدات المتاحة...",
"actions": {
"launch": "تشغيل الوحدة"
}
}
+19 -10
View File
@@ -1,11 +1,15 @@
{
"registry": {
"title": "Module Registry",
"subtitle": "Manage platform modules and their configurations",
"create_button": "Create Module",
"title": "سجل الوحدات",
"subtitle": "إدارة وحدات المنصة وتكويناتها",
"create_button": "إنشاء وحدة",
"empty_state": {
"title": "No Modules Yet",
"subtitle": "Create your first module to get started"
"title": "لا توجد وحدات بعد",
"subtitle": "قم بإنشاء وحدتك الأولى للبدء"
},
"card": {
"environments": "البيئات",
"permissions": "الأذونات"
}
},
"form": {
@@ -55,9 +59,14 @@
}
},
"delete_modal": {
"title": "Delete Module",
"description": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone and will remove all associated environments and tenant assignments.",
"confirm": "Delete Module",
"cancel": "Cancel"
}
"title": "حذف الوحدة",
"description": "هل أنت متأكد أنك تريد حذف \"{{name}}\"؟ لا يمكن التراجع عن هذا الإجراء وسيتم إزالة جميع البيئات وتعيينات المستأجرين المرتبطة.",
"confirm": "حذف الوحدة",
"cancel": "إلغاء"
},
"Inventory": "المخزون",
"Fulfillment & Logistics": "التنفيذ والخدمات اللوجستية",
"User Management": "إدارة المستخدمين",
"Tenant Management": "إدارة المستأجرين",
"Role Management": "إدارة الأدوار"
}
+67
View File
@@ -0,0 +1,67 @@
{
"title": "أنظمة الألوان",
"errors": {
"fetchFailed": "فشل جلب الأنظمة"
},
"confirmDelete": "هل أنت متأكد أنك تريد حذف هذا النظام؟",
"success": {
"deleted": "تم حذف النظام بنجاح",
"updated": "تم تحديث النظام بنجاح",
"created": "تم إنشاء النظام بنجاح"
},
"table": {
"name": "الاسم",
"default": "الافتراضي",
"preview": "المعاينة"
},
"colors": {
"primary": "اللون الأساسي",
"primary_hover": "تأثير مرور الماوس الأساسي",
"sidebar_bg": "خلفية الشريط الجانبي",
"sidebar_text": "نص الشريط الجانبي",
"sidebar_text_muted": "نص الشريط الجانبي (باهت)",
"sidebar_active_bg": "خلفية الشريط الجانبي (نشط)",
"sidebar_active_text": "نص الشريط الجانبي (نشط)",
"sidebar_hover_bg": "خلفية مرور الماوس للشريط الجانبي",
"sidebar_border": "حدود الشريط الجانبي",
"header_bg": "خلفية الرأس",
"header_text": "نص الرأس",
"header_border": "حدود الرأس",
"background": "خلفية التطبيق",
"background_secondary": "خلفية ثانوية للتطبيق",
"text_primary": "النص الأساسي",
"text_secondary": "النص الثانوي",
"card_bg": "خلفية البطاقة",
"card_border": "حدود البطاقة",
"table_header_bg": "خلفية رأس الجدول",
"table_row_hover": "تأثير مرور الوموس للجدول",
"table_border": "حدود الجدول"
},
"actions": {
"edit": "تعديل النظام",
"delete": "حذف النظام",
"create": "إنشاء نظام جديد",
"update": "تحديث النظام",
"cancel": "إلغاء"
},
"form": {
"title": {
"create": "إنشاء نظام جديد",
"edit": "تعديل النظام"
},
"fields": {
"name": {
"label": "اسم النظام",
"placeholder": "مثال: الوضع الداكن",
"required": "الاسم مطلوب"
},
"description": {
"label": "الوصف",
"placeholder": "وصف اختياري"
},
"isDefault": "تعيين كنظام افتراضي",
"sectionTitle": "تكوين الألوان",
"required": "مطلوب"
}
}
}
+7 -1
View File
@@ -1,3 +1,9 @@
{
"title": "Dashboard"
"title": "Welcome back",
"subtitle": "Manage your SaaS ecosystem from one central hub.",
"allModules": "All Modules",
"searchPlaceholder": "Search available modules...",
"actions": {
"launch": "Launch Module"
}
}
+10 -1
View File
@@ -6,6 +6,10 @@
"empty_state": {
"title": "No Modules Yet",
"subtitle": "Create your first module to get started"
},
"card": {
"environments": "Environments",
"permissions": "Permissions"
}
},
"form": {
@@ -59,5 +63,10 @@
"description": "Are you sure you want to delete \"{{name}}\"? This action cannot be undone and will remove all associated environments and tenant assignments.",
"confirm": "Delete Module",
"cancel": "Cancel"
}
},
"Inventory": "Inventory",
"Fulfillment & Logistics": "Fulfillment & Logistics",
"User Management": "User Management",
"Tenant Management": "Tenant Management",
"Role Management": "Role Management"
}
+67
View File
@@ -0,0 +1,67 @@
{
"title": "Theme Palettes",
"errors": {
"fetchFailed": "Failed to fetch palettes"
},
"confirmDelete": "Are you sure you want to delete this palette?",
"success": {
"deleted": "Palette deleted successfully",
"updated": "Palette updated successfully",
"created": "Palette created successfully"
},
"table": {
"name": "Name",
"default": "Default",
"preview": "Preview"
},
"colors": {
"primary": "Primary Color",
"primary_hover": "Primary Hover",
"sidebar_bg": "Sidebar Background",
"sidebar_text": "Sidebar Text",
"sidebar_text_muted": "Sidebar Text Muted",
"sidebar_active_bg": "Sidebar Active BG",
"sidebar_active_text": "Sidebar Active Text",
"sidebar_hover_bg": "Sidebar Hover BG",
"sidebar_border": "Sidebar Border",
"header_bg": "Header Background",
"header_text": "Header Text",
"header_border": "Header Border",
"background": "App Background",
"background_secondary": "App Secondary BG",
"text_primary": "Text Primary",
"text_secondary": "Text Secondary",
"card_bg": "Card Background",
"card_border": "Card Border",
"table_header_bg": "Table Header BG",
"table_row_hover": "Table Row Hover",
"table_border": "Table Border"
},
"actions": {
"edit": "Edit Palette",
"delete": "Delete Palette",
"create": "Create Palette",
"update": "Update Palette",
"cancel": "Cancel"
},
"form": {
"title": {
"create": "Create New Palette",
"edit": "Edit Palette"
},
"fields": {
"name": {
"label": "Palette Name",
"placeholder": "e.g. Dark Mode",
"required": "Name is required"
},
"description": {
"label": "Description",
"placeholder": "Optional description"
},
"isDefault": "Set as Default Palette",
"sectionTitle": "Color Configuration",
"required": "Required"
}
}
}