mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-01 20:10:35 +01:00
team updates
This commit is contained in:
parent
e88c69be70
commit
cf2c7517eb
@ -36,15 +36,14 @@ public class UsageRestController {
|
||||
* events to generate usage statistics.
|
||||
*
|
||||
* @param limit Optional limit on number of endpoints to return
|
||||
* @param includeHome Whether to include homepage ("/") in results
|
||||
* @param includeLogin Whether to include login page ("/login") in results
|
||||
* @param dataType Type of data to include: "all" (default), "api" (API endpoints excluding
|
||||
* auth), or "ui" (non-API endpoints)
|
||||
* @return Endpoint statistics response
|
||||
*/
|
||||
@GetMapping("/usage-endpoint-statistics")
|
||||
public ResponseEntity<EndpointStatisticsResponse> getEndpointStatistics(
|
||||
@RequestParam(value = "limit", required = false) Integer limit,
|
||||
@RequestParam(value = "includeHome", defaultValue = "true") boolean includeHome,
|
||||
@RequestParam(value = "includeLogin", defaultValue = "true") boolean includeLogin) {
|
||||
@RequestParam(value = "dataType", defaultValue = "all") String dataType) {
|
||||
|
||||
// Get all HTTP_REQUEST audit events
|
||||
List<PersistentAuditEvent> httpEvents =
|
||||
@ -56,11 +55,8 @@ public class UsageRestController {
|
||||
for (PersistentAuditEvent event : httpEvents) {
|
||||
String endpoint = extractEndpointFromAuditData(event.getData());
|
||||
if (endpoint != null) {
|
||||
// Apply filters
|
||||
if (!includeHome && "/".equals(endpoint)) {
|
||||
continue;
|
||||
}
|
||||
if (!includeLogin && "/login".equals(endpoint)) {
|
||||
// Apply data type filter
|
||||
if (!shouldIncludeEndpoint(endpoint, dataType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -171,6 +167,55 @@ public class UsageRestController {
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an endpoint should be included based on the data type filter.
|
||||
*
|
||||
* @param endpoint The endpoint path to check
|
||||
* @param dataType The filter type: "all", "api", or "ui"
|
||||
* @return true if the endpoint should be included, false otherwise
|
||||
*/
|
||||
private boolean shouldIncludeEndpoint(String endpoint, String dataType) {
|
||||
if ("all".equalsIgnoreCase(dataType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean isApiEndpoint = isApiEndpoint(endpoint);
|
||||
|
||||
if ("api".equalsIgnoreCase(dataType)) {
|
||||
return isApiEndpoint;
|
||||
} else if ("ui".equalsIgnoreCase(dataType)) {
|
||||
return !isApiEndpoint;
|
||||
}
|
||||
|
||||
// Default to including all if unrecognized type
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an endpoint is an API endpoint. API endpoints match /api/v1/* pattern but exclude
|
||||
* /api/v1/auth/* paths.
|
||||
*
|
||||
* @param endpoint The endpoint path to check
|
||||
* @return true if this is an API endpoint (excluding auth endpoints), false otherwise
|
||||
*/
|
||||
private boolean isApiEndpoint(String endpoint) {
|
||||
if (endpoint == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it starts with /api/v1/
|
||||
if (!endpoint.startsWith("/api/v1/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exclude auth endpoints
|
||||
if (endpoint.startsWith("/api/v1/auth/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// DTOs for response formatting
|
||||
|
||||
@lombok.Data
|
||||
|
||||
@ -298,6 +298,20 @@
|
||||
"general": {
|
||||
"title": "General",
|
||||
"description": "Configure general application preferences.",
|
||||
"account": "Account",
|
||||
"accountDescription": "Manage your account settings",
|
||||
"user": "User",
|
||||
"signedInAs": "Signed in as",
|
||||
"logout": "Log out",
|
||||
"enableFeatures": {
|
||||
"title": "For System Administrators",
|
||||
"intro": "Enable user authentication, team management, and workspace features for your organization.",
|
||||
"action": "Configure",
|
||||
"and": "and",
|
||||
"benefit": "Enables user roles, team collaboration, admin controls, and enterprise features.",
|
||||
"learnMore": "Learn more in documentation",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"autoUnzip": "Auto-unzip API responses",
|
||||
"autoUnzipDescription": "Automatically extract files from ZIP responses",
|
||||
"autoUnzipTooltip": "Automatically extract ZIP files returned from API operations. Disable to keep ZIP files intact. This does not affect automation workflows.",
|
||||
@ -399,8 +413,10 @@
|
||||
"top20": "Top 20",
|
||||
"all": "All",
|
||||
"refresh": "Refresh",
|
||||
"includeHomepage": "Include Homepage ('/')",
|
||||
"includeLoginPage": "Include Login Page ('/login')",
|
||||
"dataTypeLabel": "Data Type:",
|
||||
"dataTypeAll": "All",
|
||||
"dataTypeApi": "API",
|
||||
"dataTypeUi": "UI",
|
||||
"totalEndpoints": "Total Endpoints",
|
||||
"totalVisits": "Total Visits",
|
||||
"showing": "Showing",
|
||||
@ -3524,8 +3540,8 @@
|
||||
"restartingMessage": "The server is restarting. Please wait a moment...",
|
||||
"restartError": "Failed to restart server. Please restart manually.",
|
||||
"general": {
|
||||
"title": "General",
|
||||
"description": "Configure general application settings including branding and default behaviour.",
|
||||
"title": "System Settings",
|
||||
"description": "Configure system-wide application settings including branding and default behaviour.",
|
||||
"ui": "User Interface",
|
||||
"system": "System",
|
||||
"appName": "Application Name",
|
||||
@ -4812,8 +4828,12 @@
|
||||
"top20": "Top 20",
|
||||
"all": "All",
|
||||
"refresh": "Refresh",
|
||||
"includeHome": "Include Homepage ('/')",
|
||||
"includeLogin": "Include Login Page ('/login')"
|
||||
"dataTypeLabel": "Data Type:",
|
||||
"dataType": {
|
||||
"all": "All",
|
||||
"api": "API",
|
||||
"ui": "UI"
|
||||
}
|
||||
},
|
||||
"showing": {
|
||||
"top10": "Top 10",
|
||||
|
||||
@ -18,7 +18,7 @@ interface AppConfigModalProps {
|
||||
const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [active, setActive] = useState<NavKey>('overview');
|
||||
const [active, setActive] = useState<NavKey>('general');
|
||||
const isMobile = useMediaQuery("(max-width: 1024px)");
|
||||
const { config } = useAppConfig();
|
||||
|
||||
@ -27,7 +27,7 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
|
||||
const match = pathname.match(/\/settings\/([^/]+)/);
|
||||
if (match && match[1]) {
|
||||
const validSections: NavKey[] = [
|
||||
'overview', 'people', 'teams', 'general', 'hotkeys',
|
||||
'people', 'teams', 'general', 'hotkeys',
|
||||
'adminGeneral', 'adminSecurity', 'adminConnections', 'adminLegal',
|
||||
'adminPrivacy', 'adminDatabase', 'adminPremium', 'adminFeatures',
|
||||
'adminPlan', 'adminAudit', 'adminUsage', 'adminEndpoints', 'adminAdvanced'
|
||||
@ -44,8 +44,8 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
|
||||
if (opened && section) {
|
||||
setActive(section);
|
||||
} else if (opened && location.pathname.startsWith('/settings') && !section) {
|
||||
// If at /settings without a section, redirect to overview
|
||||
navigate('/settings/overview', { replace: true });
|
||||
// If at /settings without a section, redirect to general
|
||||
navigate('/settings/general', { replace: true });
|
||||
}
|
||||
}, [location.pathname, opened, navigate]);
|
||||
|
||||
@ -81,6 +81,8 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
|
||||
const isAdmin = config?.isAdmin ?? false;
|
||||
const runningEE = config?.runningEE ?? false;
|
||||
|
||||
console.log('[AppConfigModal] Config:', { isAdmin, runningEE, fullConfig: config });
|
||||
|
||||
// Left navigation structure and icons
|
||||
const configNavSections = useMemo(() =>
|
||||
createConfigNavSections(
|
||||
|
||||
@ -2,8 +2,6 @@ import React from 'react';
|
||||
import { NavKey } from '@app/components/shared/config/types';
|
||||
import HotkeysSection from '@app/components/shared/config/configSections/HotkeysSection';
|
||||
import GeneralSection from '@app/components/shared/config/configSections/GeneralSection';
|
||||
import PeopleSection from '@app/components/shared/config/configSections/PeopleSection';
|
||||
import TeamsSection from '@app/components/shared/config/configSections/TeamsSection';
|
||||
import AdminGeneralSection from '@app/components/shared/config/configSections/AdminGeneralSection';
|
||||
import AdminSecuritySection from '@app/components/shared/config/configSections/AdminSecuritySection';
|
||||
import AdminConnectionsSection from '@app/components/shared/config/configSections/AdminConnectionsSection';
|
||||
@ -49,34 +47,6 @@ export const createConfigNavSections = (
|
||||
runningEE: boolean = false
|
||||
): ConfigNavSection[] => {
|
||||
const sections: ConfigNavSection[] = [
|
||||
{
|
||||
title: 'Account',
|
||||
items: [
|
||||
{
|
||||
key: 'overview',
|
||||
label: 'Overview',
|
||||
icon: 'person-rounded',
|
||||
component: <Overview onLogoutClick={onLogoutClick} />
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Workspace',
|
||||
items: [
|
||||
{
|
||||
key: 'people',
|
||||
label: 'People',
|
||||
icon: 'group-rounded',
|
||||
component: <PeopleSection />
|
||||
},
|
||||
{
|
||||
key: 'teams',
|
||||
label: 'Teams',
|
||||
icon: 'groups-rounded',
|
||||
component: <TeamsSection />
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Preferences',
|
||||
items: [
|
||||
@ -104,7 +74,7 @@ export const createConfigNavSections = (
|
||||
items: [
|
||||
{
|
||||
key: 'adminGeneral',
|
||||
label: 'General',
|
||||
label: 'System Settings',
|
||||
icon: 'settings-rounded',
|
||||
component: <AdminGeneralSection />
|
||||
},
|
||||
|
||||
@ -165,9 +165,9 @@ export default function AdminGeneralSection() {
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<div>
|
||||
<Text fw={600} size="lg">{t('admin.settings.general.title', 'General')}</Text>
|
||||
<Text fw={600} size="lg">{t('admin.settings.general.title', 'System Settings')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('admin.settings.general.description', 'Configure general application settings including branding and default behaviour.')}
|
||||
{t('admin.settings.general.description', 'Configure system-wide application settings including branding and default behaviour.')}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ import {
|
||||
Loader,
|
||||
Alert,
|
||||
Card,
|
||||
Checkbox,
|
||||
} from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import usageAnalyticsService, { EndpointStatisticsResponse } from '@app/services/usageAnalyticsService';
|
||||
@ -22,8 +21,7 @@ const AdminUsageSection: React.FC = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [displayMode, setDisplayMode] = useState<'top10' | 'top20' | 'all'>('top10');
|
||||
const [includeHome, setIncludeHome] = useState(true);
|
||||
const [includeLogin, setIncludeLogin] = useState(true);
|
||||
const [dataType, setDataType] = useState<'all' | 'api' | 'ui'>('all');
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
@ -31,11 +29,7 @@ const AdminUsageSection: React.FC = () => {
|
||||
setError(null);
|
||||
|
||||
const limit = displayMode === 'all' ? undefined : displayMode === 'top10' ? 10 : 20;
|
||||
const response = await usageAnalyticsService.getEndpointStatistics(
|
||||
limit,
|
||||
includeHome,
|
||||
includeLogin
|
||||
);
|
||||
const response = await usageAnalyticsService.getEndpointStatistics(limit, dataType);
|
||||
|
||||
setData(response);
|
||||
} catch (err) {
|
||||
@ -47,7 +41,7 @@ const AdminUsageSection: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [displayMode, includeHome, includeLogin]);
|
||||
}, [displayMode, dataType]);
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchData();
|
||||
@ -136,15 +130,26 @@ const AdminUsageSection: React.FC = () => {
|
||||
</Group>
|
||||
|
||||
<Group>
|
||||
<Checkbox
|
||||
label={t('usage.controls.includeHome', "Include Homepage ('/')")}
|
||||
checked={includeHome}
|
||||
onChange={(event) => setIncludeHome(event.currentTarget.checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={t('usage.controls.includeLogin', "Include Login Page ('/login')")}
|
||||
checked={includeLogin}
|
||||
onChange={(event) => setIncludeLogin(event.currentTarget.checked)}
|
||||
<Text size="sm" fw={500}>
|
||||
{t('usage.controls.dataTypeLabel', 'Data Type:')}
|
||||
</Text>
|
||||
<SegmentedControl
|
||||
value={dataType}
|
||||
onChange={(value) => setDataType(value as 'all' | 'api' | 'ui')}
|
||||
data={[
|
||||
{
|
||||
value: 'all',
|
||||
label: t('usage.controls.dataType.all', 'All'),
|
||||
},
|
||||
{
|
||||
value: 'api',
|
||||
label: t('usage.controls.dataType.api', 'API'),
|
||||
},
|
||||
{
|
||||
value: 'ui',
|
||||
label: t('usage.controls.dataType.ui', 'UI'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
|
||||
@ -1,29 +1,98 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Paper, Stack, Switch, Text, Tooltip, NumberInput, SegmentedControl } from '@mantine/core';
|
||||
import { Paper, Stack, Switch, Text, Tooltip, NumberInput, SegmentedControl, Alert, Code, Group, Anchor, ActionIcon } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePreferences } from '@app/contexts/PreferencesContext';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
import type { ToolPanelMode } from '@app/constants/toolPanel';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
|
||||
const DEFAULT_AUTO_UNZIP_FILE_LIMIT = 4;
|
||||
const BANNER_DISMISSED_KEY = 'stirlingpdf_features_banner_dismissed';
|
||||
|
||||
const GeneralSection: React.FC = () => {
|
||||
interface GeneralSectionProps {
|
||||
hideTitle?: boolean;
|
||||
}
|
||||
|
||||
const GeneralSection: React.FC<GeneralSectionProps> = ({ hideTitle = false }) => {
|
||||
const { t } = useTranslation();
|
||||
const { preferences, updatePreference } = usePreferences();
|
||||
const { config } = useAppConfig();
|
||||
const [fileLimitInput, setFileLimitInput] = useState<number | string>(preferences.autoUnzipFileLimit);
|
||||
const [bannerDismissed, setBannerDismissed] = useState(() => {
|
||||
// Check localStorage on mount
|
||||
return localStorage.getItem(BANNER_DISMISSED_KEY) === 'true';
|
||||
});
|
||||
|
||||
// Sync local state with preference changes
|
||||
useEffect(() => {
|
||||
setFileLimitInput(preferences.autoUnzipFileLimit);
|
||||
}, [preferences.autoUnzipFileLimit]);
|
||||
|
||||
// Check if login is disabled
|
||||
const loginDisabled = !config?.enableLogin;
|
||||
|
||||
const handleDismissBanner = () => {
|
||||
setBannerDismissed(true);
|
||||
localStorage.setItem(BANNER_DISMISSED_KEY, 'true');
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<div>
|
||||
<Text fw={600} size="lg">{t('settings.general.title', 'General')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.description', 'Configure general application preferences.')}
|
||||
</Text>
|
||||
</div>
|
||||
{!hideTitle && (
|
||||
<div>
|
||||
<Text fw={600} size="lg">{t('settings.general.title', 'General')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.description', 'Configure general application preferences.')}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{loginDisabled && !bannerDismissed && (
|
||||
<Paper withBorder p="md" radius="md" style={{ background: 'var(--mantine-color-blue-0)', position: 'relative' }}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
style={{ position: 'absolute', top: '0.5rem', right: '0.5rem' }}
|
||||
onClick={handleDismissBanner}
|
||||
aria-label={t('settings.general.enableFeatures.dismiss', 'Dismiss')}
|
||||
>
|
||||
<LocalIcon icon="close-rounded" width="1rem" height="1rem" />
|
||||
</ActionIcon>
|
||||
<Stack gap="sm">
|
||||
<Group gap="xs">
|
||||
<LocalIcon icon="admin-panel-settings-rounded" width="1.2rem" height="1.2rem" style={{ color: 'var(--mantine-color-blue-6)' }} />
|
||||
<Text fw={600} size="sm" style={{ color: 'var(--mantine-color-blue-9)' }}>
|
||||
{t('settings.general.enableFeatures.title', 'For System Administrators')}
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.enableFeatures.intro', 'Enable user authentication, team management, and workspace features for your organization.')}
|
||||
</Text>
|
||||
<Group gap="xs" wrap="wrap">
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.enableFeatures.action', 'Configure')}
|
||||
</Text>
|
||||
<Code>SECURITY_ENABLELOGIN=true</Code>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.enableFeatures.and', 'and')}
|
||||
</Text>
|
||||
<Code>DISABLE_ADDITIONAL_FEATURES=false</Code>
|
||||
</Group>
|
||||
<Text size="xs" c="dimmed" fs="italic">
|
||||
{t('settings.general.enableFeatures.benefit', 'Enables user roles, team collaboration, admin controls, and enterprise features.')}
|
||||
</Text>
|
||||
<Anchor
|
||||
href="https://docs.stirlingpdf.com/Advanced%20Configuration/System%20and%20Security"
|
||||
target="_blank"
|
||||
size="sm"
|
||||
style={{ color: 'var(--mantine-color-blue-6)' }}
|
||||
>
|
||||
{t('settings.general.enableFeatures.learnMore', 'Learn more in documentation')} →
|
||||
</Anchor>
|
||||
</Stack>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
<Paper withBorder p="md" radius="md">
|
||||
<Stack gap="md">
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export type NavKey =
|
||||
| 'overview'
|
||||
| 'preferences'
|
||||
| 'notifications'
|
||||
| 'connections'
|
||||
|
||||
@ -23,18 +23,18 @@ const usageAnalyticsService = {
|
||||
*/
|
||||
async getEndpointStatistics(
|
||||
limit?: number,
|
||||
includeHome: boolean = true,
|
||||
includeLogin: boolean = true
|
||||
dataType: 'all' | 'api' | 'ui' = 'all'
|
||||
): Promise<EndpointStatisticsResponse> {
|
||||
const params: Record<string, any> = {
|
||||
includeHome,
|
||||
includeLogin,
|
||||
};
|
||||
const params: Record<string, any> = {};
|
||||
|
||||
if (limit !== undefined) {
|
||||
params.limit = limit;
|
||||
}
|
||||
|
||||
if (dataType !== 'all') {
|
||||
params.dataType = dataType;
|
||||
}
|
||||
|
||||
const response = await apiClient.get<EndpointStatisticsResponse>(
|
||||
'/api/v1/proprietary/ui-data/usage-endpoint-statistics',
|
||||
{ params }
|
||||
@ -47,10 +47,9 @@ const usageAnalyticsService = {
|
||||
*/
|
||||
async getChartData(
|
||||
limit?: number,
|
||||
includeHome: boolean = true,
|
||||
includeLogin: boolean = true
|
||||
dataType: 'all' | 'api' | 'ui' = 'all'
|
||||
): Promise<UsageChartData> {
|
||||
const stats = await this.getEndpointStatistics(limit, includeHome, includeLogin);
|
||||
const stats = await this.getEndpointStatistics(limit, dataType);
|
||||
|
||||
return {
|
||||
labels: stats.endpoints.map((e) => e.endpoint),
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { createConfigNavSections as createCoreConfigNavSections, ConfigNavSection } from '@core/components/shared/config/configNavSections';
|
||||
import PeopleSection from '@proprietary/components/shared/config/configSections/PeopleSection';
|
||||
import TeamsSection from '@proprietary/components/shared/config/configSections/TeamsSection';
|
||||
|
||||
/**
|
||||
* Proprietary extension of createConfigNavSections that adds workspace sections
|
||||
*/
|
||||
export const createConfigNavSections = (
|
||||
Overview: React.ComponentType<{ onLogoutClick: () => void }>,
|
||||
onLogoutClick: () => void,
|
||||
isAdmin: boolean = false,
|
||||
runningEE: boolean = false
|
||||
): ConfigNavSection[] => {
|
||||
// Get the core sections
|
||||
const sections = createCoreConfigNavSections(Overview, onLogoutClick, isAdmin, runningEE);
|
||||
|
||||
// Add Workspace section after Preferences (index 1)
|
||||
const workspaceSection: ConfigNavSection = {
|
||||
title: 'Workspace',
|
||||
items: [
|
||||
{
|
||||
key: 'people',
|
||||
label: 'People',
|
||||
icon: 'group-rounded',
|
||||
component: <PeopleSection />
|
||||
},
|
||||
{
|
||||
key: 'teams',
|
||||
label: 'Teams',
|
||||
icon: 'groups-rounded',
|
||||
component: <TeamsSection />
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Insert workspace section after Preferences (at index 1)
|
||||
sections.splice(1, 0, workspaceSection);
|
||||
|
||||
return sections;
|
||||
};
|
||||
|
||||
// Re-export types for convenience
|
||||
export type { ConfigNavSection, ConfigNavItem, ConfigColors } from '@core/components/shared/config/configNavSections';
|
||||
@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { Paper, Stack, Text, Button, Divider } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '@app/auth/UseSession';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import CoreGeneralSection from '@core/components/shared/config/configSections/GeneralSection';
|
||||
|
||||
/**
|
||||
* Proprietary extension of GeneralSection that adds account management
|
||||
*/
|
||||
const GeneralSection: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { signOut, user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await signOut();
|
||||
navigate('/login');
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<Text fw={600} size="lg">{t('settings.general.title', 'General')}</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.description', 'Configure general application preferences.')}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{user && (
|
||||
<Stack gap="xs" align="flex-end">
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('settings.general.user', 'User')}: <strong>{user.email || user.username}</strong>
|
||||
</Text>
|
||||
<Button color="red" variant="outline" size="xs" onClick={handleLogout}>
|
||||
{t('settings.general.logout', 'Log out')}
|
||||
</Button>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Render core general section preferences (without title since we show it above) */}
|
||||
<CoreGeneralSection hideTitle />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralSection;
|
||||
@ -23,7 +23,7 @@ import {
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { alert } from '@app/components/toast';
|
||||
import { userManagementService, User } from '@app/services/userManagementService';
|
||||
import { teamService, Team } from '@app/services/teamService';
|
||||
import { teamService, Team } from '@proprietary/services/teamService';
|
||||
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { alert } from '@app/components/toast';
|
||||
import { teamService, Team } from '@app/services/teamService';
|
||||
import { teamService, Team } from '@proprietary/services/teamService';
|
||||
import { User, userManagementService } from '@app/services/userManagementService';
|
||||
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
||||
|
||||
@ -19,10 +19,10 @@ import {
|
||||
} from '@mantine/core';
|
||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||
import { alert } from '@app/components/toast';
|
||||
import { teamService, Team } from '@app/services/teamService';
|
||||
import { teamService, Team } from '@proprietary/services/teamService';
|
||||
import { userManagementService, User } from '@app/services/userManagementService';
|
||||
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
||||
import TeamDetailsSection from '@app/components/shared/config/configSections/TeamDetailsSection';
|
||||
import TeamDetailsSection from '@proprietary/components/shared/config/configSections/TeamDetailsSection';
|
||||
|
||||
export default function TeamsSection() {
|
||||
const { t } = useTranslation();
|
||||
Loading…
Reference in New Issue
Block a user