Add contextual help links to admin pages
This commit is contained in:
@@ -18,6 +18,7 @@ import toast from 'react-hot-toast';
|
||||
import { adminPath } from '../constants';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { ADMIN_GRADIENTS, useAdminTheme } from './theme';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
import { extractBrandingForm, type BrandingFormValues } from '../lib/brandingForm';
|
||||
import { DEFAULT_EVENT_BRANDING } from '@/guest/context/EventBrandingContext';
|
||||
import { getContrastingTextColor, relativeLuminance } from '@/guest/lib/color';
|
||||
@@ -560,6 +561,10 @@ export default function MobileBrandingPage() {
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<XStack justifyContent="flex-end">
|
||||
<ContextHelpLink slug="event-prep-checklist" />
|
||||
</XStack>
|
||||
|
||||
<MobileCard space="$2">
|
||||
<XStack space="$2">
|
||||
<TabButton label={t('events.branding.titleShort', 'Branding')} active={activeTab === 'branding'} onPress={() => setActiveTab('branding')} />
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useEventReadiness } from './hooks/useEventReadiness';
|
||||
import { SetupChecklist } from './components/SetupChecklist';
|
||||
import { KpiStrip, PillBadge } from './components/Primitives';
|
||||
import { getApiErrorMessage } from '../lib/apiError';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
|
||||
// --- HELPERS ---
|
||||
|
||||
@@ -212,6 +213,7 @@ export default function MobileDashboardPage() {
|
||||
title={t('dashboard:overview.title', 'At a glance')}
|
||||
showSeparator={false}
|
||||
compact
|
||||
action={<ContextHelpLink slug="tenant-dashboard-overview" />}
|
||||
/>
|
||||
</YStack>
|
||||
<Separator backgroundColor={theme.border} opacity={0.6} />
|
||||
|
||||
@@ -45,6 +45,7 @@ import { useAdminTheme } from './theme';
|
||||
import { useOnlineStatus } from './hooks/useOnlineStatus';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { withAlpha } from './components/colors';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
import {
|
||||
enqueuePhotoAction,
|
||||
loadPhotoQueue,
|
||||
@@ -279,7 +280,7 @@ export default function MobileEventControlRoomPage() {
|
||||
const isMember = user?.role === 'member';
|
||||
const slug = slugParam ?? activeEvent?.slug ?? null;
|
||||
const online = useOnlineStatus();
|
||||
const { textStrong, text, muted, border, primary, surfaceMuted, surface } = useAdminTheme();
|
||||
const { textStrong, text, muted, border, primary, danger, accent, surfaceMuted, surface } = useAdminTheme();
|
||||
const [activeTab, setActiveTab] = React.useState<'moderation' | 'live'>('moderation');
|
||||
|
||||
const [moderationPhotos, setModerationPhotos] = React.useState<TenantPhoto[]>([]);
|
||||
@@ -1069,8 +1070,11 @@ export default function MobileEventControlRoomPage() {
|
||||
value={activeTab}
|
||||
onValueChange={(val) => setActiveTab(val as 'moderation' | 'live')}
|
||||
header={(
|
||||
|
||||
<MobileCard>
|
||||
<YStack space="$2">
|
||||
<XStack justifyContent="flex-end">
|
||||
<ContextHelpLink slug="live-ops-control" />
|
||||
</XStack>
|
||||
<MobileCard>
|
||||
<Accordion type="single" collapsible>
|
||||
<Accordion.Item value="upload-settings">
|
||||
<Accordion.Trigger
|
||||
@@ -1332,7 +1336,8 @@ export default function MobileEventControlRoomPage() {
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</MobileCard>
|
||||
</MobileCard>
|
||||
</YStack>
|
||||
)}
|
||||
tabs={[
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ import { resolveEventDisplayName } from '../lib/events';
|
||||
import { adminPath } from '../constants';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { useAdminTheme } from './theme';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
|
||||
type LiveShowFormState = {
|
||||
moderation_mode: NonNullable<LiveShowSettings['moderation_mode']>;
|
||||
@@ -279,6 +280,10 @@ export default function MobileEventLiveShowSettingsPage() {
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<XStack justifyContent="flex-end">
|
||||
<ContextHelpLink slug="live-show-setup" />
|
||||
</XStack>
|
||||
|
||||
{loading ? (
|
||||
<YStack space="$2">
|
||||
{Array.from({ length: 3 }).map((_, idx) => (
|
||||
|
||||
@@ -51,6 +51,7 @@ import { withAlpha } from './components/colors';
|
||||
import { useAdminTheme } from './theme';
|
||||
import { resolveEngagementMode } from '../lib/events';
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
|
||||
function allowPermission(permissions: string[], permission: string): boolean {
|
||||
if (permissions.includes('*') || permissions.includes(permission)) {
|
||||
@@ -1162,6 +1163,9 @@ export default function MobileEventTasksPage() {
|
||||
alignItems="stretch"
|
||||
width="100%"
|
||||
>
|
||||
<XStack justifyContent="flex-end" marginBottom="$2">
|
||||
<ContextHelpLink slug="event-prep-checklist" />
|
||||
</XStack>
|
||||
<Tabs.List
|
||||
borderRadius={16}
|
||||
borderWidth={1}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ADMIN_BASE_PATH, adminPath } from '../constants';
|
||||
import { resolveLayoutForFormat } from './qr/utils';
|
||||
import { useBackNavigation } from './hooks/useBackNavigation';
|
||||
import { useAdminTheme } from './theme';
|
||||
import { ContextHelpLink } from './components/ContextHelpLink';
|
||||
|
||||
export default function MobileQrPrintPage() {
|
||||
const { slug: slugParam } = useParams<{ slug?: string }>();
|
||||
@@ -101,6 +102,10 @@ export default function MobileQrPrintPage() {
|
||||
</MobileCard>
|
||||
) : null}
|
||||
|
||||
<XStack justifyContent="flex-end">
|
||||
<ContextHelpLink slug="event-prep-checklist" />
|
||||
</XStack>
|
||||
|
||||
<MobileCard space="$3" alignItems="center">
|
||||
<Text fontSize="$md" fontWeight="800" color={textStrong}>
|
||||
{t('events.qr.heroTitle', 'Entrance QR Code')}
|
||||
|
||||
@@ -65,6 +65,14 @@ vi.mock('../components/Primitives', () => ({
|
||||
</button>
|
||||
),
|
||||
SkeletonCard: () => <div>Loading...</div>,
|
||||
ContentTabs: ({ header, tabs }: { header?: React.ReactNode; tabs: Array<{ value: string; content: React.ReactNode }> }) => (
|
||||
<div>
|
||||
{header}
|
||||
{tabs.map((tab) => (
|
||||
<div key={tab.value}>{tab.content}</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../components/FormControls', () => ({
|
||||
|
||||
42
resources/js/admin/mobile/components/ContextHelpLink.tsx
Normal file
42
resources/js/admin/mobile/components/ContextHelpLink.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import { XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Pressable } from '@tamagui/react-native-web-lite';
|
||||
|
||||
import { adminPath } from '../../constants';
|
||||
import { useAdminTheme } from '../theme';
|
||||
|
||||
type ContextHelpLinkProps = {
|
||||
slug: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export function ContextHelpLink({ slug, label = 'Help' }: ContextHelpLinkProps) {
|
||||
const navigate = useNavigate();
|
||||
const { border, primary, surfaceMuted, textStrong } = useAdminTheme();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => navigate(adminPath(`/mobile/help/${encodeURIComponent(slug)}`))}
|
||||
aria-label={label}
|
||||
>
|
||||
<XStack
|
||||
alignItems="center"
|
||||
space="$1.5"
|
||||
paddingHorizontal="$2.5"
|
||||
paddingVertical="$1.5"
|
||||
borderRadius={999}
|
||||
borderWidth={1}
|
||||
borderColor={border}
|
||||
backgroundColor={surfaceMuted}
|
||||
>
|
||||
<HelpCircle size={14} color={primary} />
|
||||
<Text fontSize="$xs" fontWeight="700" color={textStrong}>
|
||||
{label}
|
||||
</Text>
|
||||
</XStack>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user