Files
fotospiel-app/resources/js/admin/mobile/components/LimitWarnings.tsx
2026-01-15 19:54:04 +01:00

132 lines
4.0 KiB
TypeScript

import React from 'react';
import { XStack, YStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { buildLimitWarnings } from '../../lib/limitWarnings';
import type { EventAddonCatalogItem, EventLimitSummary } from '../../api';
import { scopeDefaults, selectAddonKeyForScope } from '../addons';
import { CTAButton, MobileCard } from './Primitives';
import { MobileSelect } from './FormControls';
type LimitTranslator = (key: string, options?: Record<string, unknown>) => string;
export function LimitWarnings({
limits,
addons,
onCheckout,
busyScope,
translate,
textColor,
borderColor,
}: {
limits: EventLimitSummary | null;
addons: EventAddonCatalogItem[];
onCheckout: (scopeOrKey: 'photos' | 'gallery' | string) => void;
busyScope: string | null;
translate: LimitTranslator;
textColor: string;
borderColor: string;
}) {
const warnings = React.useMemo(() => buildLimitWarnings(limits, translate), [limits, translate]);
if (!warnings.length) {
return null;
}
return (
<YStack space="$2">
{warnings.map((warning) => (
<MobileCard key={warning.id} borderColor={borderColor} space="$2">
<Text fontSize="$sm" color={textColor} fontWeight="700">
{warning.message}
</Text>
{(warning.scope === 'photos' || warning.scope === 'gallery' || warning.scope === 'guests')
&& resolveAddonOptions(addons, warning.scope).length ? (
<MobileAddonsPicker
scope={warning.scope}
addons={addons}
busy={busyScope === warning.scope}
onCheckout={onCheckout}
translate={translate}
/>
) : (
<CTAButton
label={
warning.scope === 'photos'
? translate('buyMorePhotos')
: warning.scope === 'gallery'
? translate('extendGallery')
: translate('buyMoreGuests')
}
onPress={() => onCheckout(warning.scope)}
loading={busyScope === warning.scope}
/>
)}
</MobileCard>
))}
</YStack>
);
}
function resolveAddonOptions(addons: EventAddonCatalogItem[], scope: 'photos' | 'gallery' | 'guests'): EventAddonCatalogItem[] {
const whitelist = scopeDefaults[scope];
const filtered = addons.filter((addon) => addon.price_id && whitelist.includes(addon.key));
return filtered.length ? filtered : addons.filter((addon) => addon.price_id);
}
function MobileAddonsPicker({
scope,
addons,
busy,
onCheckout,
translate,
}: {
scope: 'photos' | 'gallery' | 'guests';
addons: EventAddonCatalogItem[];
busy: boolean;
onCheckout: (addonKey: string) => void;
translate: LimitTranslator;
}) {
const options = React.useMemo(() => resolveAddonOptions(addons, scope), [addons, scope]);
const [selected, setSelected] = React.useState<string>(() => options[0]?.key ?? selectAddonKeyForScope(addons, scope));
React.useEffect(() => {
if (options[0]?.key) {
setSelected(options[0].key);
}
}, [options]);
if (!options.length) {
return null;
}
return (
<XStack space="$2" alignItems="center">
<MobileSelect
value={selected}
onChange={(event) => setSelected(event.target.value)}
containerStyle={{ flex: 1, minWidth: 0 }}
compact
>
{options.map((addon) => (
<option key={addon.key} value={addon.key}>
{addon.label ?? addon.key}
</option>
))}
</MobileSelect>
<CTAButton
label={
scope === 'gallery'
? translate('extendGallery')
: scope === 'guests'
? translate('buyMoreGuests')
: translate('buyMorePhotos')
}
disabled={!selected || busy}
onPress={() => selected && onCheckout(selected)}
loading={busy}
fullWidth={false}
/>
</XStack>
);
}