feat: implement AI styling foundation and billing scope rework
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-02-06 20:01:58 +01:00
parent df00deb0df
commit 36bed12ff9
80 changed files with 8944 additions and 49 deletions

View File

@@ -3,12 +3,13 @@ import { useNavigate, useParams } from 'react-router-dom';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Button } from '@tamagui/button';
import { ArrowLeft, ChevronLeft, ChevronRight, Download, Heart, Share2 } from 'lucide-react';
import { ArrowLeft, ChevronLeft, ChevronRight, Download, Heart, Share2, Sparkles } from 'lucide-react';
import { useGesture } from '@use-gesture/react';
import { animated, to, useSpring } from '@react-spring/web';
import AppShell from '../components/AppShell';
import SurfaceCard from '../components/SurfaceCard';
import ShareSheet from '../components/ShareSheet';
import AiMagicEditSheet from '../components/AiMagicEditSheet';
import { useEventData } from '../context/EventDataContext';
import { fetchGallery, fetchPhoto, likePhoto, createPhotoShareLink } from '../services/photosApi';
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
@@ -16,6 +17,7 @@ import { useLocale } from '@/shared/guest/i18n/LocaleContext';
import { useGuestThemeVariant } from '../lib/guestTheme';
import { buildEventPath } from '../lib/routes';
import { pushGuestToast } from '../lib/toast';
import { GUEST_AI_MAGIC_EDITS_ENABLED } from '../lib/featureFlags';
type LightboxPhoto = {
id: number;
@@ -85,6 +87,7 @@ export default function PhotoLightboxScreen() {
url: null,
loading: false,
});
const [aiMagicEditOpen, setAiMagicEditOpen] = React.useState(false);
const zoomContainerRef = React.useRef<HTMLDivElement | null>(null);
const zoomImageRef = React.useRef<HTMLImageElement | null>(null);
const baseSizeRef = React.useRef({ width: 0, height: 0 });
@@ -100,6 +103,7 @@ export default function PhotoLightboxScreen() {
}));
const selected = selectedIndex !== null ? photos[selectedIndex] : null;
const hasAiStylingAccess = GUEST_AI_MAGIC_EDITS_ENABLED && Boolean(event?.capabilities?.ai_styling);
const loadPage = React.useCallback(
async (nextCursor?: string | null, replace = false) => {
@@ -381,6 +385,14 @@ export default function PhotoLightboxScreen() {
document.body.removeChild(link);
}, []);
const openAiMagicEdit = React.useCallback(() => {
if (!selected || !hasAiStylingAccess) {
return;
}
setAiMagicEditOpen(true);
}, [hasAiStylingAccess, selected]);
const bind = useGesture(
{
onDrag: ({ down, movement: [mx, my], offset: [ox, oy], last, event }) => {
@@ -642,6 +654,22 @@ export default function PhotoLightboxScreen() {
</Text>
</XStack>
</Button>
{hasAiStylingAccess ? (
<Button
unstyled
onPress={openAiMagicEdit}
paddingHorizontal="$3"
paddingVertical="$2"
aria-label={t('galleryPage.lightbox.aiMagicEditAria', 'AI Magic Edit')}
>
<XStack alignItems="center" gap="$2">
<Sparkles size={16} color={isDark ? '#F8FAFF' : '#0F172A'} />
<Text fontSize="$2" fontWeight="$6">
{t('galleryPage.lightbox.aiMagicEdit', 'AI Magic Edit')}
</Text>
</XStack>
</Button>
) : null}
</XStack>
</XStack>
</YStack>
@@ -667,6 +695,15 @@ export default function PhotoLightboxScreen() {
onCopyLink={() => copyLink(shareSheet.url)}
variant="inline"
/>
{hasAiStylingAccess ? (
<AiMagicEditSheet
open={aiMagicEditOpen}
onOpenChange={setAiMagicEditOpen}
eventToken={token ?? null}
photoId={selected?.id ?? null}
originalImageUrl={selected?.imageUrl ?? null}
/>
) : null}
</SurfaceCard>
</YStack>
</AppShell>