Replace KPI/tenant wording in admin UI and help
This commit is contained in:
@@ -155,7 +155,7 @@
|
||||
},
|
||||
"addOns": {
|
||||
"title": "Add-on-Verlauf",
|
||||
"description": "Einmalige Add-ons, die für diesen Tenant gebucht wurden.",
|
||||
"description": "Einmalige Add-ons, die für diesen Account gebucht wurden.",
|
||||
"empty": "Noch keine Add-ons gebucht.",
|
||||
"badge": "Add-ons",
|
||||
"table": {
|
||||
@@ -2031,7 +2031,7 @@
|
||||
"sections": {
|
||||
"mode": "Standard vs. Event-spezifisch",
|
||||
"toggleTitle": "Branding-Quelle wählen",
|
||||
"toggleDescription": "Nutze das Standard-Branding des Tenants oder überschreibe es nur für dieses Event.",
|
||||
"toggleDescription": "Nutze das Standard-Branding des Accounts oder überschreibe es nur für dieses Event.",
|
||||
"palette": "Palette & Modus",
|
||||
"colorsTitle": "Farben & Light/Dark",
|
||||
"colorsDescription": "Primär-, Sekundär-, Hintergrund- und Surface-Farbe festlegen.",
|
||||
@@ -2047,7 +2047,7 @@
|
||||
},
|
||||
"useDefault": "Standard nutzen",
|
||||
"useCustom": "Event-spezifisch",
|
||||
"toggleHint": "Standard übernimmt die Tenant-Farben, Event-spezifisch überschreibt sie.",
|
||||
"toggleHint": "Standard übernimmt die Account-Farben, Event-spezifisch überschreibt sie.",
|
||||
"standard": "Standard",
|
||||
"custom": "Event",
|
||||
"toggleAria": "Event-spezifisches Branding aktivieren",
|
||||
@@ -2095,10 +2095,10 @@
|
||||
"saved": "Branding gespeichert.",
|
||||
"saveError": "Branding konnte nicht gespeichert werden.",
|
||||
"footer": {
|
||||
"default": "Standard-Farben des Tenants aktiv.",
|
||||
"default": "Standard-Farben des Accounts aktiv.",
|
||||
"custom": "Event-spezifisches Branding aktiv."
|
||||
},
|
||||
"usingDefault": "Tenant-Branding aktiv",
|
||||
"usingDefault": "Account-Branding aktiv",
|
||||
"usingCustom": "Event-Branding aktiv",
|
||||
"preview": {
|
||||
"demoTitle": "Demo-Event",
|
||||
@@ -2659,10 +2659,10 @@
|
||||
"logoutTitle": "Ausloggen",
|
||||
"logoutHint": "Aus der App ausloggen"
|
||||
},
|
||||
"mobileSettings": {
|
||||
"title": "Einstellungen",
|
||||
"accountTitle": "Account",
|
||||
"tenantBadge": "Tenant #{{id}}",
|
||||
"mobileSettings": {
|
||||
"title": "Einstellungen",
|
||||
"accountTitle": "Account",
|
||||
"tenantBadge": "Konto #{{id}}",
|
||||
"notificationsTitle": "Benachrichtigungen",
|
||||
"notificationsLoading": "Lade Einstellungen ...",
|
||||
"pushTitle": "App Push",
|
||||
@@ -2833,7 +2833,7 @@
|
||||
"validation": "Füge Titel, Nachricht und ggf. einen Ziel-Gast hinzu."
|
||||
}
|
||||
},
|
||||
"dataExports": {
|
||||
"dataExports": {
|
||||
"title": "Datenexporte",
|
||||
"request": {
|
||||
"title": "Exportanfrage",
|
||||
@@ -2848,12 +2848,12 @@
|
||||
"includeMediaHint": "Größeres ZIP; nur bei Bedarf."
|
||||
},
|
||||
"scopes": {
|
||||
"tenant": "Mandantenexport",
|
||||
"tenant": "Kontoexport",
|
||||
"event": "Event-Export"
|
||||
},
|
||||
"history": {
|
||||
"title": "Letzte Exporte",
|
||||
"hint": "Die letzten 10 Exporte für Mandant und Events.",
|
||||
"hint": "Die letzten 10 Exporte für dein Konto und Events.",
|
||||
"empty": "Noch keine Exporte."
|
||||
},
|
||||
"status": {
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
},
|
||||
"addOns": {
|
||||
"title": "Add-on history",
|
||||
"description": "One-time add-ons purchased for this tenant.",
|
||||
"description": "One-time add-ons purchased for this account.",
|
||||
"empty": "No add-ons purchased yet.",
|
||||
"badge": "Add-ons",
|
||||
"table": {
|
||||
@@ -2027,7 +2027,7 @@
|
||||
"sections": {
|
||||
"mode": "Default vs. event-specific",
|
||||
"toggleTitle": "Choose branding source",
|
||||
"toggleDescription": "Use tenant defaults or override only for this event.",
|
||||
"toggleDescription": "Use account defaults or override only for this event.",
|
||||
"palette": "Palette & mode",
|
||||
"colorsTitle": "Colors & light/dark",
|
||||
"colorsDescription": "Set primary, secondary, background, and surface colors.",
|
||||
@@ -2043,7 +2043,7 @@
|
||||
},
|
||||
"useDefault": "Use default",
|
||||
"useCustom": "Event-specific",
|
||||
"toggleHint": "Default uses tenant colors; event-specific overrides them.",
|
||||
"toggleHint": "Default uses account colors; event-specific overrides them.",
|
||||
"standard": "Default",
|
||||
"custom": "Event",
|
||||
"toggleAria": "Toggle event-specific branding",
|
||||
@@ -2091,10 +2091,10 @@
|
||||
"saved": "Branding saved.",
|
||||
"saveError": "Branding could not be saved.",
|
||||
"footer": {
|
||||
"default": "Tenant default colors active.",
|
||||
"default": "Account default colors active.",
|
||||
"custom": "Event-specific branding active."
|
||||
},
|
||||
"usingDefault": "Tenant branding active",
|
||||
"usingDefault": "Account branding active",
|
||||
"usingCustom": "Event branding active",
|
||||
"preview": {
|
||||
"demoTitle": "Demo event",
|
||||
@@ -2664,7 +2664,7 @@
|
||||
"mobileSettings": {
|
||||
"title": "Settings",
|
||||
"accountTitle": "Account",
|
||||
"tenantBadge": "Tenant #{{id}}",
|
||||
"tenantBadge": "Account #{{id}}",
|
||||
"notificationsTitle": "Notifications",
|
||||
"notificationsLoading": "Loading settings ...",
|
||||
"pushTitle": "App Push",
|
||||
@@ -2835,13 +2835,13 @@
|
||||
"validation": "Add a title, message, and target guest when needed."
|
||||
}
|
||||
},
|
||||
"dataExports": {
|
||||
"dataExports": {
|
||||
"title": "Data exports",
|
||||
"request": {
|
||||
"title": "Export request",
|
||||
"hint": "Export tenant data or a specific event archive.",
|
||||
"progress": "Export is running. This list refreshes automatically."
|
||||
},
|
||||
"request": {
|
||||
"title": "Export request",
|
||||
"hint": "Export account data or a specific event archive.",
|
||||
"progress": "Export is running. This list refreshes automatically."
|
||||
},
|
||||
"fields": {
|
||||
"scope": "Scope",
|
||||
"event": "Event",
|
||||
@@ -2850,12 +2850,12 @@
|
||||
"includeMediaHint": "Bigger ZIP; choose when needed."
|
||||
},
|
||||
"scopes": {
|
||||
"tenant": "Tenant export",
|
||||
"tenant": "Account export",
|
||||
"event": "Event export"
|
||||
},
|
||||
"history": {
|
||||
"title": "Recent exports",
|
||||
"hint": "Latest 10 exports for your tenant and events.",
|
||||
"hint": "Latest 10 exports for your account and events.",
|
||||
"empty": "No exports yet."
|
||||
},
|
||||
"status": {
|
||||
|
||||
@@ -685,7 +685,7 @@ export default function MobileBrandingPage() {
|
||||
</XStack>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{form.useDefaultBranding
|
||||
? t('events.branding.usingDefault', 'Tenant-Branding aktiv')
|
||||
? t('events.branding.usingDefault', 'Account-Branding aktiv')
|
||||
: t('events.branding.usingCustom', 'Event-Branding aktiv')}
|
||||
</Text>
|
||||
</MobileCard>
|
||||
|
||||
@@ -197,7 +197,7 @@ export function DataExportsPanel({
|
||||
{t('dataExports.request.title', 'Export request')}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('dataExports.request.hint', 'Export tenant data or a specific event archive.')}
|
||||
{t('dataExports.request.hint', 'Export account data or a specific event archive.')}
|
||||
</Text>
|
||||
<YStack space="$2">
|
||||
{!isRecap ? (
|
||||
@@ -211,7 +211,7 @@ export function DataExportsPanel({
|
||||
compact
|
||||
style={{ minWidth: 140, maxWidth: 180 }}
|
||||
>
|
||||
<option value="tenant">{t('dataExports.scopes.tenant', 'Tenant')}</option>
|
||||
<option value="tenant">{t('dataExports.scopes.tenant', 'Account')}</option>
|
||||
<option value="event">{t('dataExports.scopes.event', 'Event')}</option>
|
||||
</MobileSelect>
|
||||
</XStack>
|
||||
@@ -281,7 +281,7 @@ export function DataExportsPanel({
|
||||
{t('dataExports.history.title', 'Recent exports')}
|
||||
</Text>
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
{t('dataExports.history.hint', 'Latest 10 exports for your tenant and events.')}
|
||||
{t('dataExports.history.hint', 'Latest 10 exports for your account and events.')}
|
||||
</Text>
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
@@ -301,7 +301,7 @@ export function DataExportsPanel({
|
||||
<Text fontSize="$sm" fontWeight="700" color={textStrong}>
|
||||
{entry.scope === 'event'
|
||||
? t('dataExports.scopes.event', 'Event export')
|
||||
: t('dataExports.scopes.tenant', 'Tenant export')}
|
||||
: t('dataExports.scopes.tenant', 'Account export')}
|
||||
</Text>
|
||||
{entry.event ? (
|
||||
<Text fontSize="$xs" color={muted}>
|
||||
|
||||
@@ -221,7 +221,7 @@ export default function MobileSettingsPage() {
|
||||
{user?.name ?? user?.email ?? t('settings.session.unknown', 'Benutzer')}
|
||||
</Text>
|
||||
{user?.tenant_id ? (
|
||||
<PillBadge tone="muted">{t('mobileSettings.tenantBadge', 'Tenant #{{id}}', { id: user.tenant_id })}</PillBadge>
|
||||
<PillBadge tone="muted">{t('mobileSettings.tenantBadge', 'Account #{{id}}', { id: user.tenant_id })}</PillBadge>
|
||||
) : null}
|
||||
<XStack space="$2">
|
||||
<CTAButton label={t('settings.profile.actions.openProfile', 'Profil bearbeiten')} onPress={() => navigate(ADMIN_PROFILE_ACCOUNT_PATH)} />
|
||||
|
||||
119
resources/js/admin/mobile/__tests__/DataExportsPage.test.tsx
Normal file
119
resources/js/admin/mobile/__tests__/DataExportsPage.test.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
|
||||
const tMock = (
|
||||
_key: string,
|
||||
fallback?: string | Record<string, unknown>,
|
||||
options?: Record<string, unknown>,
|
||||
) => {
|
||||
let value = typeof fallback === 'string' ? fallback : _key;
|
||||
if (options) {
|
||||
Object.entries(options).forEach(([key, val]) => {
|
||||
value = value.replaceAll(`{{${key}}}`, String(val));
|
||||
});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => navigateMock,
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: tMock }),
|
||||
initReactI18next: {
|
||||
type: '3rdParty',
|
||||
init: () => undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useBackNavigation', () => ({
|
||||
useBackNavigation: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../theme', () => ({
|
||||
useAdminTheme: () => ({
|
||||
textStrong: '#111827',
|
||||
text: '#111827',
|
||||
muted: '#6b7280',
|
||||
border: '#e5e7eb',
|
||||
danger: '#b91c1c',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../components/MobileShell', () => ({
|
||||
MobileShell: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
HeaderActionButton: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/Primitives', () => ({
|
||||
MobileCard: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CTAButton: ({ label, onPress }: { label: string; onPress?: () => void }) => (
|
||||
<button type="button" onClick={onPress}>
|
||||
{label}
|
||||
</button>
|
||||
),
|
||||
PillBadge: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
SkeletonCard: () => <div>Loading...</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/FormControls', () => ({
|
||||
MobileSelect: ({ children, ...props }: { children: React.ReactNode }) => (
|
||||
<select {...props}>{children}</select>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/switch', () => ({
|
||||
Switch: Object.assign(
|
||||
({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
{ Thumb: () => <div /> },
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/react-native-web-lite', () => ({
|
||||
Pressable: ({
|
||||
children,
|
||||
onPress,
|
||||
...rest
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
onPress?: () => void;
|
||||
[key: string]: unknown;
|
||||
}) => (
|
||||
<button type="button" onClick={onPress} {...rest}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
FlatList: ({ data = [], renderItem }: { data?: unknown[]; renderItem?: (info: any) => React.ReactNode }) => (
|
||||
<div>{renderItem ? data.map((item, index) => renderItem({ item, index })) : null}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../../api', () => ({
|
||||
listTenantDataExports: vi.fn().mockResolvedValue([]),
|
||||
getEvents: vi.fn().mockResolvedValue([]),
|
||||
requestTenantDataExport: vi.fn(),
|
||||
downloadTenantDataExport: vi.fn(),
|
||||
}));
|
||||
|
||||
import MobileDataExportsPage from '../DataExportsPage';
|
||||
|
||||
describe('MobileDataExportsPage', () => {
|
||||
it('renders account scope label', async () => {
|
||||
render(<MobileDataExportsPage />);
|
||||
|
||||
expect(await screen.findByText('Account')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
152
resources/js/admin/mobile/__tests__/SettingsPage.test.tsx
Normal file
152
resources/js/admin/mobile/__tests__/SettingsPage.test.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
const logoutMock = vi.fn();
|
||||
|
||||
const tMock = (
|
||||
_key: string,
|
||||
fallback?: string | Record<string, unknown>,
|
||||
options?: Record<string, unknown>,
|
||||
) => {
|
||||
let value = typeof fallback === 'string' ? fallback : _key;
|
||||
if (options) {
|
||||
Object.entries(options).forEach(([key, val]) => {
|
||||
value = value.replaceAll(`{{${key}}}`, String(val));
|
||||
});
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => navigateMock,
|
||||
}));
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: tMock }),
|
||||
initReactI18next: {
|
||||
type: '3rdParty',
|
||||
init: () => undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../auth/context', () => ({
|
||||
useAuth: () => ({ user: { name: 'Test User', tenant_id: 123 }, logout: logoutMock }),
|
||||
}));
|
||||
|
||||
vi.mock('../../api', () => ({
|
||||
getNotificationPreferences: vi.fn().mockResolvedValue({ defaults: {}, preferences: {} }),
|
||||
updateNotificationPreferences: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../components/MobileShell', () => ({
|
||||
MobileShell: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/Primitives', () => ({
|
||||
MobileCard: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
CTAButton: ({ label, onPress }: { label: string; onPress?: () => void }) => (
|
||||
<button type="button" onClick={onPress}>
|
||||
{label}
|
||||
</button>
|
||||
),
|
||||
PillBadge: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('../components/MobileInstallBanner', () => ({
|
||||
MobileInstallBanner: () => null,
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useAdminPushSubscription', () => ({
|
||||
useAdminPushSubscription: () => ({ supported: false, permission: 'default', subscribed: false }),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useDevicePermissions', () => ({
|
||||
useDevicePermissions: () => ({
|
||||
loading: false,
|
||||
notifications: 'granted',
|
||||
camera: 'granted',
|
||||
storage: 'available',
|
||||
requestPersistentStorage: vi.fn().mockResolvedValue(false),
|
||||
refresh: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useInstallPrompt', () => ({
|
||||
useInstallPrompt: () => ({
|
||||
isInstalled: false,
|
||||
isStandalone: false,
|
||||
canInstall: false,
|
||||
isIos: false,
|
||||
promptInstall: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useBackNavigation', () => ({
|
||||
useBackNavigation: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../hooks/useOnlineStatus', () => ({
|
||||
useOnlineStatus: () => true,
|
||||
}));
|
||||
|
||||
vi.mock('../lib/installBanner', () => ({
|
||||
getInstallBannerDismissed: () => false,
|
||||
setInstallBannerDismissed: () => undefined,
|
||||
shouldShowInstallBanner: () => false,
|
||||
}));
|
||||
|
||||
vi.mock('../lib/mobileTour', () => ({
|
||||
setTourSeen: () => undefined,
|
||||
}));
|
||||
|
||||
vi.mock('../theme', () => ({
|
||||
useAdminTheme: () => ({
|
||||
text: '#111827',
|
||||
muted: '#6b7280',
|
||||
border: '#e5e7eb',
|
||||
danger: '#b91c1c',
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/group', () => ({
|
||||
YGroup: Object.assign(({ children }: { children: React.ReactNode }) => <div>{children}</div>, {
|
||||
Item: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/list-item', () => ({
|
||||
ListItem: ({ title, subTitle }: { title?: React.ReactNode; subTitle?: React.ReactNode }) => (
|
||||
<div>
|
||||
{title}
|
||||
{subTitle}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/switch', () => ({
|
||||
Switch: Object.assign(
|
||||
({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
{ Thumb: () => <div /> },
|
||||
),
|
||||
}));
|
||||
|
||||
import MobileSettingsPage from '../SettingsPage';
|
||||
|
||||
describe('MobileSettingsPage', () => {
|
||||
it('renders account badge for the current user', async () => {
|
||||
render(<MobileSettingsPage />);
|
||||
|
||||
expect(await screen.findByText('Account #123')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user