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",
|
path: "/tenants",
|
||||||
access: "superadmin.tenant.read",
|
access: "superadmin.tenant.read",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: <UsersRound size={22} />,
|
|
||||||
name: "Users",
|
|
||||||
path: "/users",
|
|
||||||
access: "admin.user.read",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: <UserCog size={22} />,
|
icon: <UserCog size={22} />,
|
||||||
name: "Roles",
|
name: "Roles",
|
||||||
path: "/roles",
|
path: "/roles",
|
||||||
access: "admin.role.read",
|
access: "admin.role.read",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: <UsersRound size={22} />,
|
||||||
|
name: "Users",
|
||||||
|
path: "/users",
|
||||||
|
access: "admin.user.read",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: <Palette size={22} />,
|
icon: <Palette size={22} />,
|
||||||
name: "Themes",
|
name: "Themes",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ProfilePage from "../application/profile/ProfilePage";
|
|||||||
import Tenants from "../application/tenants";
|
import Tenants from "../application/tenants";
|
||||||
import ProtectedRoutes from "./ProtectedRoutes";
|
import ProtectedRoutes from "./ProtectedRoutes";
|
||||||
import Theme from "../application/theme";
|
import Theme from "../application/theme";
|
||||||
|
import SettingsPage from "../application/settings/SettingsPage";
|
||||||
const AppRoutes = () => {
|
const AppRoutes = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -24,11 +25,10 @@ const AppRoutes = () => {
|
|||||||
<Route path="/tenants/*" element={<Tenants />} />
|
<Route path="/tenants/*" element={<Tenants />} />
|
||||||
<Route path="/roles/*" element={<Roles />} />
|
<Route path="/roles/*" element={<Roles />} />
|
||||||
<Route path="/theme/*" element={<Theme />} />
|
<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 path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Catch all - Redirect to dashboard which will handle auth or show dashboard */}
|
|
||||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user