Merge pull request 'feat: Added Settings Page and Multi Language Support' (#6) from furqan into dev
Reviewed-on: https://gitea.maskantech.in/gitea_admin/saas_frontend/pulls/6
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'react-toastify';
|
||||
import { CustomDropdown, CustomConfirmationModal } from '../../components/custom';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { languages } from '../../i18n/config';
|
||||
import type { SupportedLanguage } from '../../i18n/config';
|
||||
import { Globe } from 'lucide-react';
|
||||
|
||||
const SettingsPage: React.FC = () => {
|
||||
const { t, i18n } = useTranslation('common');
|
||||
const { user, updateLanguage } = useAuth();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedLanguage] = useState<SupportedLanguage>(
|
||||
(i18n.language as SupportedLanguage) || 'en'
|
||||
);
|
||||
const [pendingLanguage, setPendingLanguage] = useState<SupportedLanguage | null>(null);
|
||||
const [showConfirmation, setShowConfirmation] = useState(false);
|
||||
|
||||
const handleLanguageChange = (newLanguage: SupportedLanguage) => {
|
||||
if (newLanguage === selectedLanguage) return;
|
||||
|
||||
setPendingLanguage(newLanguage);
|
||||
setShowConfirmation(true);
|
||||
};
|
||||
|
||||
const handleConfirmLanguageChange = async () => {
|
||||
if (!pendingLanguage) return;
|
||||
|
||||
setIsLoading(true);
|
||||
setShowConfirmation(false);
|
||||
|
||||
try {
|
||||
localStorage.setItem('preferred_language', pendingLanguage);
|
||||
|
||||
if (user) {
|
||||
await updateLanguage(pendingLanguage);
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('Failed to update language:', error);
|
||||
toast.error(t('messages.error'));
|
||||
setIsLoading(false);
|
||||
setPendingLanguage(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancelLanguageChange = () => {
|
||||
setPendingLanguage(null);
|
||||
setShowConfirmation(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
{/* Page Header */}
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-(--text-primary)">
|
||||
{t('settings.title')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Language Settings Section */}
|
||||
<div className="bg-(--card-bg) rounded-lg border border-(--card-border) p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Globe className="w-5 h-5 text-(--primary)" />
|
||||
<h2 className="text-lg font-semibold text-(--text-primary)">
|
||||
{t('settings.language.title')}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-(--text-secondary) mb-6">
|
||||
{t('settings.language.description')}
|
||||
</p>
|
||||
|
||||
<div className="max-w-md">
|
||||
<CustomDropdown
|
||||
label={t('settings.language.selectLabel') || "Select Language"}
|
||||
options={(Object.keys(languages) as SupportedLanguage[]).map(lang => ({
|
||||
label: languages[lang].name,
|
||||
value: lang
|
||||
}))}
|
||||
value={selectedLanguage}
|
||||
onChange={(e) => handleLanguageChange(e.target.value as SupportedLanguage)}
|
||||
disabled={isLoading}
|
||||
leftIcon={<Globe size={18} />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Confirmation Modal */}
|
||||
<CustomConfirmationModal
|
||||
isOpen={showConfirmation}
|
||||
onClose={handleCancelLanguageChange}
|
||||
onConfirm={handleConfirmLanguageChange}
|
||||
title={t('settings.language.confirmTitle') || 'Change Language?'}
|
||||
description={t('settings.language.confirmMessage') || 'The page will reload to apply the new language settings. Continue?'}
|
||||
confirmText={t('actions.confirm')}
|
||||
cancelText={t('actions.cancel')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsPage;
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface LanguageSettings {
|
||||
code: string;
|
||||
name: string;
|
||||
dir: 'ltr' | 'rtl';
|
||||
}
|
||||
|
||||
export interface SettingsFormData {
|
||||
language: string;
|
||||
}
|
||||
@@ -51,18 +51,18 @@ const navItems: NavItem[] = [
|
||||
path: "/tenants",
|
||||
access: "superadmin.tenant.read",
|
||||
},
|
||||
{
|
||||
icon: <UsersRound size={22} />,
|
||||
name: "Users",
|
||||
path: "/users",
|
||||
access: "admin.user.read",
|
||||
},
|
||||
{
|
||||
icon: <UserCog size={22} />,
|
||||
name: "Roles",
|
||||
path: "/roles",
|
||||
access: "admin.role.read",
|
||||
},
|
||||
{
|
||||
icon: <UsersRound size={22} />,
|
||||
name: "Users",
|
||||
path: "/users",
|
||||
access: "admin.user.read",
|
||||
},
|
||||
{
|
||||
icon: <Palette size={22} />,
|
||||
name: "Themes",
|
||||
|
||||
@@ -9,6 +9,7 @@ import ProfilePage from "../application/profile/ProfilePage";
|
||||
import Tenants from "../application/tenants";
|
||||
import ProtectedRoutes from "./ProtectedRoutes";
|
||||
import Theme from "../application/theme";
|
||||
import SettingsPage from "../application/settings/SettingsPage";
|
||||
const AppRoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
@@ -24,11 +25,10 @@ const AppRoutes = () => {
|
||||
<Route path="/tenants/*" element={<Tenants />} />
|
||||
<Route path="/roles/*" element={<Roles />} />
|
||||
<Route path="/theme/*" element={<Theme />} />
|
||||
{/* Redirect root to dashboard if logged in (ProtectedRoutes handles auth check usually, or we redirect to signin if not) */}
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||
</Route>
|
||||
|
||||
{/* Catch all - Redirect to dashboard which will handle auth or show dashboard */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user