132 lines
4.0 KiB
TypeScript
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>
|
|
);
|
|
}
|