Add package summary banner
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-06 12:01:12 +01:00
parent a796973861
commit cc89cc667a
5 changed files with 204 additions and 13 deletions

View File

@@ -17,6 +17,7 @@ import { useAdminPushSubscription } from './hooks/useAdminPushSubscription';
import { useDevicePermissions } from './hooks/useDevicePermissions';
import { useInstallPrompt } from './hooks/useInstallPrompt';
import { getTourSeen, resolveTourStepKeys, setTourSeen, type TourStepKey } from './lib/mobileTour';
import { formatPackageLimit, getPackageFeatureLabel } from './lib/packageSummary';
import { trackOnboarding } from '../api';
import { useAuth } from '../auth/context';
import { ADMIN_ACTION_COLORS, ADMIN_MOTION, useAdminTheme } from './theme';
@@ -350,6 +351,11 @@ export default function MobileDashboardPage() {
locale={locale}
/>
) : null;
const showPackageSummaryBanner =
Boolean(activePackage && summarySeenPackageId === activePackage.id) &&
!summaryOpen &&
!packagesLoading &&
!packagesError;
React.useEffect(() => {
if (events.length || isLoading || fallbackLoading || fallbackAttempted) {
@@ -387,6 +393,12 @@ export default function MobileDashboardPage() {
if (!effectiveHasEvents) {
return (
<MobileShell activeTab="home" title={t('mobileDashboard.title', 'Dashboard')}>
{showPackageSummaryBanner ? (
<PackageSummaryBanner
packageName={activePackage?.package_name}
onOpen={() => setSummaryOpen(true)}
/>
) : null}
<OnboardingEmptyState
installPrompt={installPrompt}
pushState={pushState}
@@ -406,6 +418,12 @@ export default function MobileDashboardPage() {
title={t('mobileDashboard.title', 'Dashboard')}
subtitle={t('mobileDashboard.selectEvent', 'Select an event to continue')}
>
{showPackageSummaryBanner ? (
<PackageSummaryBanner
packageName={activePackage?.package_name}
onOpen={() => setSummaryOpen(true)}
/>
) : null}
<EventPickerList events={effectiveEvents} locale={locale} text={text} muted={muted} border={border} />
{tourSheet}
{packageSummarySheet}
@@ -419,6 +437,12 @@ export default function MobileDashboardPage() {
title={resolveEventDisplayName(activeEvent ?? undefined)}
subtitle={formatEventDate(activeEvent?.event_date, locale) ?? undefined}
>
{showPackageSummaryBanner ? (
<PackageSummaryBanner
packageName={activePackage?.package_name}
onOpen={() => setSummaryOpen(true)}
/>
) : null}
<DeviceSetupCard
installPrompt={installPrompt}
pushState={pushState}
@@ -486,11 +510,6 @@ function PackageSummarySheet({
const galleryDays = (limits as Record<string, number | null> | null)?.gallery_days ?? null;
const hasFeatures = Array.isArray(features) && features.length > 0;
const formatLimit = (value: number | null) =>
value === null || value === undefined
? t('mobileDashboard.packageSummary.unlimited', 'Unlimited')
: String(value);
const formatDate = (value?: string | null) => formatEventDate(value, locale) ?? t('mobileDashboard.packageSummary.unknown', 'Unknown');
return (
@@ -506,12 +525,12 @@ function PackageSummarySheet({
</Text>
</YStack>
<YStack space="$2" marginTop="$2">
<SummaryRow label={t('mobileDashboard.packageSummary.limitPhotos', 'Photos')} value={formatLimit(maxPhotos)} />
<SummaryRow label={t('mobileDashboard.packageSummary.limitGuests', 'Guests')} value={formatLimit(maxGuests)} />
<SummaryRow label={t('mobileDashboard.packageSummary.limitDays', 'Gallery days')} value={formatLimit(galleryDays)} />
<SummaryRow label={t('mobileDashboard.packageSummary.limitPhotos', 'Photos')} value={formatPackageLimit(maxPhotos, t)} />
<SummaryRow label={t('mobileDashboard.packageSummary.limitGuests', 'Guests')} value={formatPackageLimit(maxGuests, t)} />
<SummaryRow label={t('mobileDashboard.packageSummary.limitDays', 'Gallery days')} value={formatPackageLimit(galleryDays, t)} />
<SummaryRow
label={t('mobileDashboard.packageSummary.remaining', 'Remaining events')}
value={remainingEvents === null || remainingEvents === undefined ? t('mobileDashboard.packageSummary.unlimited', 'Unlimited') : String(remainingEvents)}
value={formatPackageLimit(remainingEvents, t)}
/>
<SummaryRow label={t('mobileDashboard.packageSummary.purchased', 'Purchased')} value={formatDate(purchasedAt)} />
{expiresAt ? (
@@ -520,7 +539,7 @@ function PackageSummarySheet({
</YStack>
</MobileCard>
{hasFeatures ? (
{hasFeatures ? (
<MobileCard space="$2" borderColor={border} backgroundColor={surface}>
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('mobileDashboard.packageSummary.featuresTitle', 'Included features')}
@@ -532,7 +551,7 @@ function PackageSummarySheet({
<Sparkles size={14} color={primary} />
</XStack>
<Text fontSize="$xs" color={text}>
{t(`mobileDashboard.packageSummary.feature.${feature}`, feature)}
{getPackageFeatureLabel(feature, t)}
</Text>
</XStack>
))}
@@ -569,6 +588,54 @@ function SummaryRow({ label, value }: { label: string; value: string }) {
);
}
function PackageSummaryBanner({
packageName,
onOpen,
}: {
packageName?: string | null;
onOpen: () => void;
}) {
const { t } = useTranslation('management');
const { textStrong, muted, border, surface, accentSoft, primary } = useAdminTheme();
const text = textStrong;
return (
<MobileCard space="$2" borderColor={border} backgroundColor={surface}>
<XStack alignItems="center" justifyContent="space-between" gap="$2">
<XStack alignItems="center" space="$2" flex={1}>
<XStack
width={36}
height={36}
borderRadius={12}
backgroundColor={accentSoft}
alignItems="center"
justifyContent="center"
>
<Sparkles size={16} color={primary} />
</XStack>
<YStack space="$0.5" flex={1}>
<Text fontSize="$sm" fontWeight="800" color={text}>
{t('mobileDashboard.packageSummary.bannerTitle', 'Your package summary')}
</Text>
<Text fontSize="$xs" color={muted}>
{t('mobileDashboard.packageSummary.bannerSubtitle', {
name: packageName ?? t('mobileDashboard.packageSummary.fallbackTitle', 'Package summary'),
defaultValue: '{{name}} is active. Review limits & features.',
})}
</Text>
</YStack>
</XStack>
<CTAButton
label={t('mobileDashboard.packageSummary.bannerCta', 'View')}
tone="ghost"
fullWidth={false}
onPress={onOpen}
/>
</XStack>
</MobileCard>
);
}
function DeviceSetupCard({ installPrompt, pushState, devicePermissions, onOpenSettings }: DeviceSetupProps) {
const { t } = useTranslation('management');
const { textStrong, muted, border, primary, accentSoft } = useAdminTheme();