Fix language setting not being saved to database on admin settings page

- Added default_language field to general settings state in SettingsPage
- Replaced LanguageSelector component with simple select dropdown on settings page
- Fixed public settings endpoint to read general_default_language from database
- Language setting now properly saved when clicking Save Settings button
- Setting is correctly used by gallery login page and legal pages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-08 09:49:45 +02:00
parent cfa0b0da69
commit 2012b0bab9
91 changed files with 6183 additions and 577 deletions
@@ -0,0 +1,48 @@
import { api } from '../config/api';
export interface PhotoCategory {
id: number;
name: string;
slug: string;
is_global: boolean;
event_id: number | null;
created_at: string;
}
export interface CreateCategoryData {
name: string;
slug?: string;
is_global?: boolean;
event_id?: number;
}
export const categoriesService = {
// Get all global categories
async getGlobalCategories(): Promise<PhotoCategory[]> {
const response = await api.get<PhotoCategory[]>('/api/admin/categories/global');
return response.data;
},
// Get categories for a specific event (global + event-specific)
async getEventCategories(eventId: number): Promise<PhotoCategory[]> {
const response = await api.get<PhotoCategory[]>(`/api/admin/categories/event/${eventId}`);
return response.data;
},
// Create a new category
async createCategory(data: CreateCategoryData): Promise<PhotoCategory> {
const response = await api.post<PhotoCategory>('/api/admin/categories', data);
return response.data;
},
// Update a category
async updateCategory(id: number, name: string): Promise<PhotoCategory> {
const response = await api.put<PhotoCategory>(`/api/admin/categories/${id}`, { name });
return response.data;
},
// Delete a category
async deleteCategory(id: number): Promise<void> {
await api.delete(`/api/admin/categories/${id}`);
}
};
+39
View File
@@ -0,0 +1,39 @@
import { api } from '../config/api';
export interface CMSPage {
id: number;
slug: string;
title_en: string;
title_de: string;
content_en: string;
content_de: string;
updated_at: string;
}
export const cmsService = {
// Get all CMS pages
async getPages(): Promise<CMSPage[]> {
const response = await api.get<CMSPage[]>('/api/admin/cms/pages');
return response.data;
},
// Get a single CMS page
async getPage(slug: string): Promise<CMSPage> {
const response = await api.get<CMSPage>(`/api/admin/cms/pages/${slug}`);
return response.data;
},
// Update a CMS page
async updatePage(slug: string, data: Partial<CMSPage>): Promise<CMSPage> {
const response = await api.put<CMSPage>(`/api/admin/cms/pages/${slug}`, data);
return response.data;
},
// Get public CMS page (no auth required)
async getPublicPage(slug: string, lang: string = 'en'): Promise<{ title: string; content: string }> {
const response = await api.get<{ title: string; content: string }>(`/api/public/pages/${slug}`, {
params: { lang }
});
return response.data;
}
};
+2 -1
View File
@@ -5,4 +5,5 @@ export { adminService } from './admin.service';
export { analyticsService } from './analytics.service';
export { archiveService } from './archive.service';
export { emailService } from './email.service';
export { settingsService } from './settings.service';
export { settingsService } from './settings.service';
export { cmsService } from './cms.service';
+50 -4
View File
@@ -6,7 +6,12 @@ export interface BrandingSettings {
support_email: string;
footer_text: string;
watermark_enabled: boolean;
watermark_position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
watermark_opacity?: number;
watermark_size?: number;
watermark_logo_url?: string;
logo_url?: string;
favicon_url?: string;
}
export interface ThemeSettings {
@@ -50,11 +55,11 @@ export const settingsService = {
},
// Upload logo
async uploadLogo(file: File): Promise<{ logo_url: string }> {
async uploadLogo(file: File): Promise<string> {
const formData = new FormData();
formData.append('logo', file);
const response = await api.post<{ message: string; logo_url: string }>(
const response = await api.post<{ logoUrl: string }>(
'/api/admin/settings/logo',
formData,
{
@@ -64,7 +69,43 @@ export const settingsService = {
}
);
return { logo_url: response.data.logo_url };
return response.data.logoUrl;
},
// Upload favicon
async uploadFavicon(file: File): Promise<string> {
const formData = new FormData();
formData.append('favicon', file);
const response = await api.post<{ faviconUrl: string }>(
'/api/admin/settings/favicon',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
return response.data.faviconUrl;
},
// Upload watermark logo
async uploadWatermarkLogo(file: File): Promise<string> {
const formData = new FormData();
formData.append('watermarkLogo', file);
const response = await api.post<{ watermarkLogoUrl: string }>(
'/api/admin/settings/branding/watermark-logo',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
return response.data.watermarkLogoUrl;
},
// Update theme settings
@@ -91,7 +132,12 @@ export const settingsService = {
support_email: rawSettings.branding_support_email || '',
footer_text: rawSettings.branding_footer_text || '',
watermark_enabled: rawSettings.branding_watermark_enabled || false,
logo_url: rawSettings.branding_logo_url || undefined
watermark_position: rawSettings.branding_watermark_position || 'bottom-right',
watermark_opacity: rawSettings.branding_watermark_opacity || 50,
watermark_size: rawSettings.branding_watermark_size || 15,
watermark_logo_url: rawSettings.branding_watermark_logo_url || undefined,
logo_url: rawSettings.branding_logo_url || undefined,
favicon_url: rawSettings.branding_favicon_url || undefined
};
},
+30
View File
@@ -0,0 +1,30 @@
import { toast as toastify } from 'react-toastify';
import i18n from '../i18n/config';
export const toast = {
success: (messageKey: string, interpolations?: Record<string, any>) => {
const message = i18n.t(messageKey, interpolations);
toastify.success(message);
},
error: (messageKey: string, interpolations?: Record<string, any>) => {
const message = i18n.t(messageKey, interpolations);
toastify.error(message);
},
info: (messageKey: string, interpolations?: Record<string, any>) => {
const message = i18n.t(messageKey, interpolations);
toastify.info(message);
},
warning: (messageKey: string, interpolations?: Record<string, any>) => {
const message = i18n.t(messageKey, interpolations);
toastify.warning(message);
},
// For direct messages (not translation keys)
successDirect: (message: string) => toastify.success(message),
errorDirect: (message: string) => toastify.error(message),
infoDirect: (message: string) => toastify.info(message),
warningDirect: (message: string) => toastify.warning(message),
};