diff --git a/resources/js/admin/i18n/locales/de/management.json b/resources/js/admin/i18n/locales/de/management.json index b4ff436..8032e74 100644 --- a/resources/js/admin/i18n/locales/de/management.json +++ b/resources/js/admin/i18n/locales/de/management.json @@ -2074,9 +2074,25 @@ "unlimited": "Unbegrenzt", "unknown": "Unbekannt", "featuresTitle": "Enthaltene Features", + "feature": { + "priority_support": "Priority Support", + "custom_domain": "Eigene Domain", + "analytics": "Analytics", + "team_management": "Team-Management", + "moderation_tools": "Moderations-Tools", + "prints": "Print-Uploads", + "photo_likes_enabled": "Foto-Likes", + "event_checklist": "Event-Checkliste", + "advanced_analytics": "Erweiterte Analytics", + "branding_allowed": "Branding", + "watermark_allowed": "Wasserzeichen" + }, "hint": "Im Billing kannst du dein Paket jederzeit prüfen oder upgraden.", "continue": "Weiter zum Event-Setup", - "dismiss": "Schließen" + "dismiss": "Schließen", + "bannerTitle": "Deine Paketübersicht", + "bannerSubtitle": "{{name}} ist aktiv. Prüfe Limits & Features.", + "bannerCta": "Ansehen" }, "pickEvent": "Event auswählen", "status": { diff --git a/resources/js/admin/i18n/locales/en/management.json b/resources/js/admin/i18n/locales/en/management.json index 374e7f1..f7a1a97 100644 --- a/resources/js/admin/i18n/locales/en/management.json +++ b/resources/js/admin/i18n/locales/en/management.json @@ -2078,9 +2078,25 @@ "unlimited": "Unlimited", "unknown": "Unknown", "featuresTitle": "Included features", + "feature": { + "priority_support": "Priority support", + "custom_domain": "Custom domain", + "analytics": "Analytics", + "team_management": "Team management", + "moderation_tools": "Moderation tools", + "prints": "Print uploads", + "photo_likes_enabled": "Photo likes", + "event_checklist": "Event checklist", + "advanced_analytics": "Advanced analytics", + "branding_allowed": "Branding", + "watermark_allowed": "Watermarks" + }, "hint": "You can revisit billing any time to review or upgrade your package.", "continue": "Continue to event setup", - "dismiss": "Close" + "dismiss": "Close", + "bannerTitle": "Your package summary", + "bannerSubtitle": "{{name}} is active. Review limits & features.", + "bannerCta": "View" }, "pickEvent": "Select an event", "status": { diff --git a/resources/js/admin/mobile/DashboardPage.tsx b/resources/js/admin/mobile/DashboardPage.tsx index afee791..38c6553 100644 --- a/resources/js/admin/mobile/DashboardPage.tsx +++ b/resources/js/admin/mobile/DashboardPage.tsx @@ -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 ( + {showPackageSummaryBanner ? ( + setSummaryOpen(true)} + /> + ) : null} + {showPackageSummaryBanner ? ( + setSummaryOpen(true)} + /> + ) : null} {tourSheet} {packageSummarySheet} @@ -419,6 +437,12 @@ export default function MobileDashboardPage() { title={resolveEventDisplayName(activeEvent ?? undefined)} subtitle={formatEventDate(activeEvent?.event_date, locale) ?? undefined} > + {showPackageSummaryBanner ? ( + setSummaryOpen(true)} + /> + ) : 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({ - - - + + + {expiresAt ? ( @@ -520,7 +539,7 @@ function PackageSummarySheet({ - {hasFeatures ? ( + {hasFeatures ? ( {t('mobileDashboard.packageSummary.featuresTitle', 'Included features')} @@ -532,7 +551,7 @@ function PackageSummarySheet({ - {t(`mobileDashboard.packageSummary.feature.${feature}`, feature)} + {getPackageFeatureLabel(feature, t)} ))} @@ -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 ( + + + + + + + + + {t('mobileDashboard.packageSummary.bannerTitle', 'Your package summary')} + + + {t('mobileDashboard.packageSummary.bannerSubtitle', { + name: packageName ?? t('mobileDashboard.packageSummary.fallbackTitle', 'Package summary'), + defaultValue: '{{name}} is active. Review limits & features.', + })} + + + + + + + ); +} + function DeviceSetupCard({ installPrompt, pushState, devicePermissions, onOpenSettings }: DeviceSetupProps) { const { t } = useTranslation('management'); const { textStrong, muted, border, primary, accentSoft } = useAdminTheme(); diff --git a/resources/js/admin/mobile/lib/packageSummary.test.ts b/resources/js/admin/mobile/lib/packageSummary.test.ts new file mode 100644 index 0000000..f5025da --- /dev/null +++ b/resources/js/admin/mobile/lib/packageSummary.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; +import { formatPackageLimit, getPackageFeatureLabel } from './packageSummary'; + +const t = (key: string, options?: Record | string) => { + if (typeof options === 'string') { + return options; + } + return (options?.defaultValue as string | undefined) ?? key; +}; + +describe('packageSummary helpers', () => { + it('returns translated labels for known features', () => { + expect(getPackageFeatureLabel('priority_support', t)).toBe('Priority support'); + }); + + it('falls back to raw feature key for unknown features', () => { + expect(getPackageFeatureLabel('custom_feature', t)).toBe('custom_feature'); + }); + + it('formats unlimited package limits', () => { + expect(formatPackageLimit(null, t)).toBe('Unlimited'); + }); + + it('formats numeric package limits', () => { + expect(formatPackageLimit(12, t)).toBe('12'); + }); +}); diff --git a/resources/js/admin/mobile/lib/packageSummary.ts b/resources/js/admin/mobile/lib/packageSummary.ts new file mode 100644 index 0000000..afee5ca --- /dev/null +++ b/resources/js/admin/mobile/lib/packageSummary.ts @@ -0,0 +1,65 @@ +type Translate = (key: string, options?: Record | string) => string; + +const FEATURE_LABELS: Record = { + priority_support: { + key: 'mobileDashboard.packageSummary.feature.priority_support', + fallback: 'Priority support', + }, + custom_domain: { + key: 'mobileDashboard.packageSummary.feature.custom_domain', + fallback: 'Custom domain', + }, + analytics: { + key: 'mobileDashboard.packageSummary.feature.analytics', + fallback: 'Analytics', + }, + team_management: { + key: 'mobileDashboard.packageSummary.feature.team_management', + fallback: 'Team management', + }, + moderation_tools: { + key: 'mobileDashboard.packageSummary.feature.moderation_tools', + fallback: 'Moderation tools', + }, + prints: { + key: 'mobileDashboard.packageSummary.feature.prints', + fallback: 'Print uploads', + }, + photo_likes_enabled: { + key: 'mobileDashboard.packageSummary.feature.photo_likes_enabled', + fallback: 'Photo likes', + }, + event_checklist: { + key: 'mobileDashboard.packageSummary.feature.event_checklist', + fallback: 'Event checklist', + }, + advanced_analytics: { + key: 'mobileDashboard.packageSummary.feature.advanced_analytics', + fallback: 'Advanced analytics', + }, + branding_allowed: { + key: 'mobileDashboard.packageSummary.feature.branding_allowed', + fallback: 'Branding', + }, + watermark_allowed: { + key: 'mobileDashboard.packageSummary.feature.watermark_allowed', + fallback: 'Watermarks', + }, +}; + +export function getPackageFeatureLabel(feature: string, t: Translate): string { + const entry = FEATURE_LABELS[feature]; + if (entry) { + return t(entry.key, entry.fallback); + } + + return t(`mobileDashboard.packageSummary.feature.${feature}`, feature); +} + +export function formatPackageLimit(value: number | null | undefined, t: Translate): string { + if (value === null || value === undefined) { + return t('mobileDashboard.packageSummary.unlimited', 'Unlimited'); + } + + return String(value); +}