refactor(guest): retire legacy guest app and move shared modules
This commit is contained in:
@@ -7,6 +7,7 @@ import { ConsentProvider } from '@/contexts/consent';
|
||||
import { AppearanceProvider } from '@/hooks/use-appearance';
|
||||
import { useAppearance } from '@/hooks/use-appearance';
|
||||
import ToastHost from './components/ToastHost';
|
||||
import PwaManager from './components/PwaManager';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
@@ -27,6 +28,7 @@ function AppThemeRouter() {
|
||||
return (
|
||||
<Theme name={themeName}>
|
||||
<RouterProvider router={router} />
|
||||
<PwaManager />
|
||||
<ToastHost />
|
||||
</Theme>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
XStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
YStack: ({ children, ...props }: { children: React.ReactNode }) => <div {...props}>{children}</div>,
|
||||
XStack: ({ children, ...props }: { children: React.ReactNode }) => <div {...props}>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@tamagui/text', () => ({
|
||||
@@ -23,7 +24,7 @@ vi.mock('../components/AppShell', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/components/PullToRefresh', () => ({
|
||||
vi.mock('@/shared/guest/components/PullToRefresh', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
@@ -35,7 +36,7 @@ vi.mock('../context/GuestIdentityContext', () => ({
|
||||
useOptionalGuestIdentity: () => ({ name: 'Alex' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: unknown, fallback?: string) => {
|
||||
if (typeof fallback === 'string') return fallback;
|
||||
@@ -45,12 +46,12 @@ vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('../lib/guestTheme', () => ({
|
||||
useGuestThemeVariant: () => ({ isDark: false }),
|
||||
useGuestThemeVariant: vi.fn(() => ({ isDark: false })),
|
||||
}));
|
||||
|
||||
vi.mock('../lib/bento', () => ({
|
||||
@@ -70,7 +71,7 @@ vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/lib/localizeTaskLabel', () => ({
|
||||
vi.mock('@/shared/guest/lib/localizeTaskLabel', () => ({
|
||||
localizeTaskLabel: (value: string | null) => value,
|
||||
}));
|
||||
|
||||
@@ -116,6 +117,7 @@ vi.mock('../services/achievementsApi', () => ({
|
||||
}));
|
||||
|
||||
import AchievementsScreen from '../screens/AchievementsScreen';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
describe('AchievementsScreen', () => {
|
||||
it('renders personal achievements content', async () => {
|
||||
@@ -123,6 +125,16 @@ describe('AchievementsScreen', () => {
|
||||
|
||||
expect(await screen.findByText('Badges')).toBeInTheDocument();
|
||||
expect(screen.getByText('First upload')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload photo')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload one photo')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses dark-mode feed row and placeholder styles when dark theme is active', async () => {
|
||||
vi.mocked(useGuestThemeVariant).mockReturnValue({ isDark: true });
|
||||
const { container } = render(<AchievementsScreen />);
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Feed' }));
|
||||
|
||||
expect(container.querySelector('[backgroundcolor="rgba(255, 255, 255, 0.08)"]')).toBeTruthy();
|
||||
expect(container.querySelector('[backgroundcolor="rgba(255, 255, 255, 0.12)"]')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'demo' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string) => fallback ?? _key,
|
||||
}),
|
||||
|
||||
@@ -15,17 +15,17 @@ vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ event: null }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/context/EventBrandingContext', () => ({
|
||||
vi.mock('@/shared/guest/context/EventBrandingContext', () => ({
|
||||
EventBrandingProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
LocaleProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
DEFAULT_LOCALE: 'de',
|
||||
isLocaleCode: () => true,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/context/NotificationCenterContext', () => ({
|
||||
vi.mock('@/shared/guest/context/NotificationCenterContext', () => ({
|
||||
NotificationCenterProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { EventBrandingProvider } from '@/guest/context/EventBrandingContext';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import { EventBrandingProvider } from '@/shared/guest/context/EventBrandingContext';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
|
||||
vi.mock('@tamagui/stacks', () => ({
|
||||
YStack: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
|
||||
@@ -22,7 +22,7 @@ vi.mock('../hooks/usePollStats', () => ({
|
||||
usePollStats: () => ({ stats: { onlineGuests: 0, guestCount: 0, likesCount: 0 } }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: unknown, fallback?: string) => {
|
||||
if (typeof fallback === 'string') return fallback;
|
||||
@@ -32,7 +32,7 @@ vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -35,19 +35,19 @@ vi.mock('../components/SurfaceCard', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/components/PullToRefresh', () => ({
|
||||
vi.mock('@/shared/guest/components/PullToRefresh', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/helpApi', () => ({
|
||||
vi.mock('@/shared/guest/services/helpApi', () => ({
|
||||
getHelpArticles: () => Promise.resolve({
|
||||
servedFromCache: false,
|
||||
articles: [{ slug: 'intro', title: 'Intro', summary: 'Summary', updated_at: null }],
|
||||
|
||||
@@ -57,7 +57,7 @@ vi.mock('../components/PhotoFrameTile', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/context/EventBrandingContext', () => ({
|
||||
vi.mock('@/shared/guest/context/EventBrandingContext', () => ({
|
||||
useEventBranding: () => ({
|
||||
branding: { welcomeMessage: '' },
|
||||
isCustom: false,
|
||||
@@ -97,11 +97,11 @@ const translate = (key: string, options?: unknown, fallback?: string) => {
|
||||
return key;
|
||||
};
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: translate, locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
@@ -119,7 +119,7 @@ vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'light' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useGuestTaskProgress', () => ({
|
||||
vi.mock('@/shared/guest/hooks/useGuestTaskProgress', () => ({
|
||||
useGuestTaskProgress: () => ({ isCompleted: () => false }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { LocaleProvider } from '@/guest/i18n/LocaleContext';
|
||||
import { LocaleProvider } from '@/shared/guest/i18n/LocaleContext';
|
||||
|
||||
vi.mock('react-router-dom', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
|
||||
@@ -10,7 +10,7 @@ vi.mock('@tamagui/text', () => ({
|
||||
SizableText: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, fallback?: string) => fallback ?? _key,
|
||||
}),
|
||||
|
||||
@@ -47,14 +47,14 @@ vi.mock('../services/photosApi', () => ({
|
||||
createPhotoShareLink: vi.fn().mockResolvedValue({ url: 'http://example.com' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ vi.mock('../services/uploadApi', () => ({
|
||||
uploadPhoto: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
vi.mock('@/shared/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
@@ -89,7 +89,7 @@ vi.mock('../hooks/usePollGalleryDelta', () => ({
|
||||
usePollGalleryDelta: () => ({ data: { photos: [], latestPhotoAt: null, nextCursor: null }, loading: false, error: null }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
@@ -97,7 +97,7 @@ vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de', availableLocales: [], setLocale: vi.fn() }),
|
||||
}));
|
||||
|
||||
@@ -129,7 +129,7 @@ vi.mock('../services/qrApi', () => ({
|
||||
fetchEventQrCode: () => Promise.resolve({ qr_code_data_url: null }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useGuestTaskProgress', () => ({
|
||||
vi.mock('@/shared/guest/hooks/useGuestTaskProgress', () => ({
|
||||
useGuestTaskProgress: () => ({ completedCount: 0 }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ appearance: 'dark', updateAppearance }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de', availableLocales: [], setLocale: vi.fn() }),
|
||||
}));
|
||||
|
||||
@@ -24,7 +24,7 @@ vi.mock('../context/GuestIdentityContext', () => ({
|
||||
useOptionalGuestIdentity: () => ({ hydrated: false, name: '', setName: vi.fn(), clearName: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useHapticsPreference', () => ({
|
||||
vi.mock('@/shared/guest/hooks/useHapticsPreference', () => ({
|
||||
useHapticsPreference: () => ({ enabled: false, setEnabled: vi.fn(), supported: true }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key }),
|
||||
}));
|
||||
|
||||
@@ -10,11 +10,11 @@ vi.mock('@/hooks/use-appearance', () => ({
|
||||
useAppearance: () => ({ resolved: 'dark' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/components/legal-markdown', () => ({
|
||||
vi.mock('@/shared/guest/components/legal-markdown', () => ({
|
||||
LegalMarkdown: () => <div>Legal markdown</div>,
|
||||
}));
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ vi.mock('../lib/toast', () => ({
|
||||
pushGuestToast: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: unknown, fallback?: string) => {
|
||||
if (typeof fallback === 'string') return fallback;
|
||||
|
||||
@@ -12,7 +12,7 @@ vi.mock('../services/photosApi', () => ({
|
||||
fetchGallery: () => Promise.resolve({ data: [] }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ vi.mock('../services/tasksApi', () => ({
|
||||
fetchTasks: () => Promise.resolve([{ id: 12, title: 'Capture the dancefloor', description: 'Find the happiest crew.' }]),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({ t: (_key: string, fallback?: string) => fallback ?? _key, locale: 'de' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -42,11 +42,11 @@ vi.mock('../context/EventDataContext', () => ({
|
||||
useEventData: () => ({ token: 'token' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
vi.mock('@/shared/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? key) : (arg3 ?? key),
|
||||
@@ -54,7 +54,7 @@ vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/LocaleContext', () => ({
|
||||
vi.mock('@/shared/guest/i18n/LocaleContext', () => ({
|
||||
useLocale: () => ({ locale: 'de' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ vi.mock('../services/tasksApi', () => ({
|
||||
fetchTasks: vi.fn().mockResolvedValue([{ id: 12, title: 'Capture the dancefloor', description: 'Find the happiest crew.' }]),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/services/pendingUploadsApi', () => ({
|
||||
vi.mock('@/shared/guest/services/pendingUploadsApi', () => ({
|
||||
fetchPendingUploadsSummary: vi.fn().mockResolvedValue({ items: [], totalCount: 0 }),
|
||||
}));
|
||||
|
||||
@@ -46,11 +46,11 @@ vi.mock('../context/GuestIdentityContext', () => ({
|
||||
useOptionalGuestIdentity: () => ({ name: 'Alex' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/hooks/useGuestTaskProgress', () => ({
|
||||
vi.mock('@/shared/guest/hooks/useGuestTaskProgress', () => ({
|
||||
useGuestTaskProgress: () => ({ markCompleted: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock('@/guest/i18n/useTranslation', () => ({
|
||||
vi.mock('@/shared/guest/i18n/useTranslation', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (_key: string, arg2?: Record<string, string | number> | string, arg3?: string) =>
|
||||
typeof arg2 === 'string' || arg2 === undefined ? (arg2 ?? arg3 ?? _key) : (arg3 ?? _key),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
||||
import { resolveGuestThemeName } from '../lib/brandingTheme';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
|
||||
const baseBranding: EventBranding = {
|
||||
primaryColor: '#FF5A5F',
|
||||
|
||||
@@ -12,8 +12,8 @@ import SettingsSheet from './SettingsSheet';
|
||||
import GuestAnalyticsNudge from './GuestAnalyticsNudge';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { useOptionalNotificationCenter } from '@/guest/context/NotificationCenterContext';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useOptionalNotificationCenter } from '@/shared/guest/context/NotificationCenterContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
type AppShellProps = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@tamagui/button';
|
||||
import { Home, Image, Share2 } from 'lucide-react';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
export default function BottomDock() {
|
||||
|
||||
@@ -2,9 +2,9 @@ import React from 'react';
|
||||
import { YStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Camera, Heart, PartyPopper, Users } from 'lucide-react';
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/guest/context/EventBrandingContext';
|
||||
import { getContrastingTextColor } from '@/guest/lib/color';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { getContrastingTextColor } from '@/shared/guest/lib/color';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
|
||||
type LogoSize = 's' | 'm' | 'l';
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { useConsent } from '@/contexts/consent';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { isUploadPath, shouldShowAnalyticsNudge } from '@/guest/lib/analyticsConsent';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { isUploadPath, shouldShowAnalyticsNudge } from '@/shared/guest/lib/analyticsConsent';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
const PROMPT_STORAGE_KEY = 'fotospiel.guest.analyticsPrompt';
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { ScrollView } from '@tamagui/scroll-view';
|
||||
import { X } from 'lucide-react';
|
||||
import { useOptionalNotificationCenter } from '@/guest/context/NotificationCenterContext';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useOptionalNotificationCenter } from '@/shared/guest/context/NotificationCenterContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
type NotificationSheetProps = {
|
||||
|
||||
64
resources/js/guest-v2/components/PwaManager.tsx
Normal file
64
resources/js/guest-v2/components/PwaManager.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { registerSW } from 'virtual:pwa-register';
|
||||
import { pushGuestToast } from '../lib/toast';
|
||||
|
||||
export default function PwaManager() {
|
||||
const updatePromptedRef = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateSW = registerSW({
|
||||
immediate: true,
|
||||
onNeedRefresh() {
|
||||
if (updatePromptedRef.current) {
|
||||
return;
|
||||
}
|
||||
updatePromptedRef.current = true;
|
||||
pushGuestToast({
|
||||
text: 'Update available',
|
||||
type: 'info',
|
||||
durationMs: 0,
|
||||
action: {
|
||||
label: 'Reload',
|
||||
onClick: () => updateSW(true),
|
||||
},
|
||||
});
|
||||
},
|
||||
onOfflineReady() {
|
||||
pushGuestToast({
|
||||
text: 'Offline mode ready',
|
||||
type: 'success',
|
||||
});
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.warn('Guest v2 PWA registration failed', error);
|
||||
},
|
||||
});
|
||||
|
||||
const runQueue = () => {
|
||||
void import('@/shared/guest/queue/queue')
|
||||
.then((module) => module.processQueue().catch(() => {}))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'sync-queue') {
|
||||
runQueue();
|
||||
}
|
||||
};
|
||||
|
||||
navigator.serviceWorker.addEventListener('message', handleMessage);
|
||||
window.addEventListener('online', runQueue);
|
||||
runQueue();
|
||||
|
||||
return () => {
|
||||
navigator.serviceWorker.removeEventListener('message', handleMessage);
|
||||
window.removeEventListener('online', runQueue);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import { Input } from '@tamagui/input';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { Switch } from '@tamagui/switch';
|
||||
import { Check, Moon, RotateCcw, Sun, Languages, FileText, LifeBuoy } from 'lucide-react';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import { useHapticsPreference } from '@/guest/hooks/useHapticsPreference';
|
||||
import { triggerHaptic } from '@/guest/lib/haptics';
|
||||
import { useHapticsPreference } from '@/shared/guest/hooks/useHapticsPreference';
|
||||
import { triggerHaptic } from '@/shared/guest/lib/haptics';
|
||||
import { useConsent } from '@/contexts/consent';
|
||||
import { useAppearance } from '@/hooks/use-appearance';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
|
||||
@@ -6,10 +6,10 @@ import { Button } from '@tamagui/button';
|
||||
import { ArrowLeft, X } from 'lucide-react';
|
||||
import SettingsContent from './SettingsContent';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { LegalMarkdown } from '@/guest/components/legal-markdown';
|
||||
import type { LocaleCode } from '@/guest/i18n/messages';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { LegalMarkdown } from '@/shared/guest/components/legal-markdown';
|
||||
import type { LocaleCode } from '@/shared/guest/i18n/messages';
|
||||
|
||||
const legalLinks = [
|
||||
{ slug: 'impressum', labelKey: 'settings.legal.section.impressum', fallback: 'Impressum' },
|
||||
|
||||
@@ -4,7 +4,7 @@ import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Share2, MessageSquare, Copy, X } from 'lucide-react';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
type ShareSheetProps = {
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Camera, CheckCircle2, Heart, RefreshCw, Sparkles, Timer as TimerIcon } from 'lucide-react';
|
||||
import PhotoFrameTile from './PhotoFrameTile';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { getEmotionIcon, getEmotionTheme, type EmotionIdentity } from '@/guest/lib/emotionTheme';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { getEmotionIcon, getEmotionTheme, type EmotionIdentity } from '@/shared/guest/lib/emotionTheme';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Bell, Settings } from 'lucide-react';
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/guest/context/EventBrandingContext';
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/shared/guest/context/EventBrandingContext';
|
||||
import EventLogo from './EventLogo';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { fetchEvent, type EventData, FetchEventError } from '../services/eventApi';
|
||||
import { isTaskModeEnabled } from '@/guest/lib/engagement';
|
||||
import { isTaskModeEnabled } from '@/shared/guest/lib/engagement';
|
||||
|
||||
type EventDataStatus = 'idle' | 'loading' | 'ready' | 'error';
|
||||
|
||||
|
||||
168
resources/js/guest-v2/guest-sw.ts
Normal file
168
resources/js/guest-v2/guest-sw.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import { clientsClaim } from 'workbox-core';
|
||||
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
|
||||
import { ExpirationPlugin } from 'workbox-expiration';
|
||||
import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching';
|
||||
import { registerRoute } from 'workbox-routing';
|
||||
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
|
||||
import { shouldCacheResponse } from './lib/cachePolicy';
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope & {
|
||||
__WB_MANIFEST: Array<unknown>;
|
||||
};
|
||||
|
||||
clientsClaim();
|
||||
precacheAndRoute(self.__WB_MANIFEST);
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
const isGuestNavigation = (pathname: string) => {
|
||||
if (pathname === '/event') {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/e/')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/g/')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/show/')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/setup/')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/share/')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/help')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/legal')) {
|
||||
return true;
|
||||
}
|
||||
if (pathname.startsWith('/settings')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) =>
|
||||
request.mode === 'navigate' && url.origin === self.location.origin && isGuestNavigation(url.pathname),
|
||||
new NetworkFirst({
|
||||
cacheName: 'guest-pages',
|
||||
networkTimeoutSeconds: 5,
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 40, maxAgeSeconds: 60 * 60 * 24 * 7 }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) =>
|
||||
request.method === 'GET' &&
|
||||
url.origin === self.location.origin &&
|
||||
url.pathname.startsWith('/api/v1/'),
|
||||
new NetworkFirst({
|
||||
cacheName: 'guest-api',
|
||||
networkTimeoutSeconds: 6,
|
||||
plugins: [
|
||||
{
|
||||
cacheWillUpdate: async ({ response }) => (shouldCacheResponse(response) ? response : null),
|
||||
},
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 80, maxAgeSeconds: 60 * 60 * 24 }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) => request.destination === 'image' && url.origin === self.location.origin,
|
||||
new CacheFirst({
|
||||
cacheName: 'guest-images',
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
registerRoute(
|
||||
({ request, url }) => request.destination === 'font' && url.origin === self.location.origin,
|
||||
new StaleWhileRevalidate({
|
||||
cacheName: 'guest-fonts',
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [0, 200] }),
|
||||
new ExpirationPlugin({ maxEntries: 30, maxAgeSeconds: 60 * 60 * 24 * 365 }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data?.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('sync', (event: unknown) => {
|
||||
const syncEvent = event as { tag?: string; waitUntil: (promise: Promise<unknown>) => void };
|
||||
if (syncEvent.tag === 'upload-queue') {
|
||||
syncEvent.waitUntil(
|
||||
(async () => {
|
||||
const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' });
|
||||
clients.forEach((client) => client.postMessage({ type: 'sync-queue' }));
|
||||
})(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
const payload = event.data?.json?.() ?? {};
|
||||
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
const title = payload.title ?? 'Neue Nachricht';
|
||||
const options = {
|
||||
body: payload.body ?? '',
|
||||
icon: '/apple-touch-icon.png',
|
||||
badge: '/apple-touch-icon.png',
|
||||
data: payload.data ?? {},
|
||||
};
|
||||
|
||||
await self.registration.showNotification(title, options);
|
||||
|
||||
const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
||||
clients.forEach((client) => client.postMessage({ type: 'guest-notification-refresh' }));
|
||||
})(),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
const targetUrl = event.notification.data?.url || '/';
|
||||
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
|
||||
for (const client of clientList) {
|
||||
if ('focus' in client) {
|
||||
client.navigate(targetUrl);
|
||||
return client.focus();
|
||||
}
|
||||
}
|
||||
if (self.clients.openWindow) {
|
||||
return self.clients.openWindow(targetUrl);
|
||||
}
|
||||
return undefined;
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('pushsubscriptionchange', (event) => {
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {
|
||||
clientList.forEach((client) => client.postMessage({ type: 'push-subscription-change' }));
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Navigate, Outlet, useParams } from 'react-router-dom';
|
||||
import { LocaleProvider } from '@/guest/i18n/LocaleContext';
|
||||
import { DEFAULT_LOCALE, isLocaleCode } from '@/guest/i18n/messages';
|
||||
import { NotificationCenterProvider } from '@/guest/context/NotificationCenterContext';
|
||||
import { EventBrandingProvider } from '@/guest/context/EventBrandingContext';
|
||||
import { LocaleProvider } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { DEFAULT_LOCALE, isLocaleCode } from '@/shared/guest/i18n/messages';
|
||||
import { NotificationCenterProvider } from '@/shared/guest/context/NotificationCenterContext';
|
||||
import { EventBrandingProvider } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { EventDataProvider, useEventData } from '../context/EventDataContext';
|
||||
import { GuestIdentityProvider, useOptionalGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import { mapEventBranding } from '../lib/eventBranding';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { LocaleProvider } from '@/guest/i18n/LocaleContext';
|
||||
import { DEFAULT_LOCALE } from '@/guest/i18n/messages';
|
||||
import { LocaleProvider } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { DEFAULT_LOCALE } from '@/shared/guest/i18n/messages';
|
||||
|
||||
export default function GuestLocaleLayout() {
|
||||
return (
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Theme } from '@tamagui/core';
|
||||
import React from 'react';
|
||||
import type { Appearance } from '@/hooks/use-appearance';
|
||||
import { useAppearance } from '@/hooks/use-appearance';
|
||||
import { useEventBranding } from '@/guest/context/EventBrandingContext';
|
||||
import { relativeLuminance } from '@/guest/lib/color';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import { useEventBranding } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { relativeLuminance } from '@/shared/guest/lib/color';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
|
||||
const LIGHT_LUMINANCE_THRESHOLD = 0.65;
|
||||
const DARK_LUMINANCE_THRESHOLD = 0.35;
|
||||
|
||||
20
resources/js/guest-v2/lib/cachePolicy.ts
Normal file
20
resources/js/guest-v2/lib/cachePolicy.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export function shouldCacheResponse(response: Response | null): boolean {
|
||||
if (!response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cacheControl = response.headers.get('Cache-Control') ?? '';
|
||||
const pragma = response.headers.get('Pragma') ?? '';
|
||||
const normalizedCacheControl = cacheControl.toLowerCase();
|
||||
const normalizedPragma = pragma.toLowerCase();
|
||||
|
||||
if (normalizedCacheControl.includes('no-store') || normalizedCacheControl.includes('private')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalizedPragma.includes('no-cache')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import type { EventBrandingPayload } from '@/guest/services/eventApi';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
import type { EventBrandingPayload } from '@/shared/guest/services/eventApi';
|
||||
|
||||
export function mapEventBranding(raw?: EventBrandingPayload | null): EventBranding | null {
|
||||
if (!raw) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/guest/context/EventBrandingContext';
|
||||
import type { EventBranding } from '@/guest/types/event-branding';
|
||||
import { DEFAULT_EVENT_BRANDING, useOptionalEventBranding } from '@/shared/guest/context/EventBrandingContext';
|
||||
import type { EventBranding } from '@/shared/guest/types/event-branding';
|
||||
import { useAppearance, type Appearance } from '@/hooks/use-appearance';
|
||||
import { resolveGuestThemeName } from './brandingTheme';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Award, BarChart2, Camera, Flame, Sparkles, Trophy, Users } from 'lucide-react';
|
||||
import AppShell from '../components/AppShell';
|
||||
import PullToRefresh from '@/guest/components/PullToRefresh';
|
||||
import PullToRefresh from '@/shared/guest/components/PullToRefresh';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import {
|
||||
@@ -17,11 +17,11 @@ import {
|
||||
type TopPhotoHighlight,
|
||||
type TrendingEmotionHighlight,
|
||||
} from '../services/achievementsApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
import { localizeTaskLabel } from '@/guest/lib/localizeTaskLabel';
|
||||
import { localizeTaskLabel } from '@/shared/guest/lib/localizeTaskLabel';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@@ -76,9 +76,10 @@ type LeaderboardProps = {
|
||||
emptyCopy: string;
|
||||
formatNumber: (value: number) => string;
|
||||
guestFallback: string;
|
||||
isDark: boolean;
|
||||
};
|
||||
|
||||
function Leaderboard({ title, description, icon: Icon, entries, emptyCopy, formatNumber, guestFallback }: LeaderboardProps) {
|
||||
function Leaderboard({ title, description, icon: Icon, entries, emptyCopy, formatNumber, guestFallback, isDark }: LeaderboardProps) {
|
||||
return (
|
||||
<YStack gap="$2">
|
||||
<XStack alignItems="center" gap="$2">
|
||||
@@ -114,9 +115,9 @@ function Leaderboard({ title, description, icon: Icon, entries, emptyCopy, forma
|
||||
justifyContent="space-between"
|
||||
padding="$2"
|
||||
borderRadius="$card"
|
||||
backgroundColor="rgba(15, 23, 42, 0.05)"
|
||||
backgroundColor={isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.05)'}
|
||||
borderWidth={1}
|
||||
borderColor="rgba(15, 23, 42, 0.08)"
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.16)' : 'rgba(15, 23, 42, 0.08)'}
|
||||
>
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<Text fontSize="$1" fontWeight="$7" color="$color" opacity={0.7}>
|
||||
@@ -146,9 +147,10 @@ type BadgesGridProps = {
|
||||
badges: AchievementBadge[];
|
||||
emptyCopy: string;
|
||||
completeCopy: string;
|
||||
isDark: boolean;
|
||||
};
|
||||
|
||||
function BadgesGrid({ badges, emptyCopy, completeCopy }: BadgesGridProps) {
|
||||
function BadgesGrid({ badges, emptyCopy, completeCopy, isDark }: BadgesGridProps) {
|
||||
if (badges.length === 0) {
|
||||
return (
|
||||
<Text fontSize="$2" color="$color" opacity={0.7}>
|
||||
@@ -172,8 +174,8 @@ function BadgesGrid({ badges, emptyCopy, completeCopy }: BadgesGridProps) {
|
||||
padding="$2"
|
||||
borderRadius="$card"
|
||||
borderWidth={1}
|
||||
borderColor={badge.earned ? 'rgba(16, 185, 129, 0.4)' : 'rgba(15, 23, 42, 0.1)'}
|
||||
backgroundColor={badge.earned ? 'rgba(16, 185, 129, 0.08)' : 'rgba(255, 255, 255, 0.85)'}
|
||||
borderColor={badge.earned ? 'rgba(16, 185, 129, 0.4)' : isDark ? 'rgba(255, 255, 255, 0.16)' : 'rgba(15, 23, 42, 0.1)'}
|
||||
backgroundColor={badge.earned ? 'rgba(16, 185, 129, 0.08)' : isDark ? 'rgba(255, 255, 255, 0.05)' : 'rgba(255, 255, 255, 0.85)'}
|
||||
gap="$1"
|
||||
>
|
||||
<Text fontSize="$2" fontWeight="$7">
|
||||
@@ -190,7 +192,7 @@ function BadgesGrid({ badges, emptyCopy, completeCopy }: BadgesGridProps) {
|
||||
{badge.earned ? completeCopy : `${progress}/${target}`}
|
||||
</Text>
|
||||
</XStack>
|
||||
<YStack height={6} borderRadius={999} backgroundColor="rgba(15, 23, 42, 0.08)">
|
||||
<YStack height={6} borderRadius={999} backgroundColor={isDark ? 'rgba(255, 255, 255, 0.16)' : 'rgba(15, 23, 42, 0.08)'}>
|
||||
<YStack
|
||||
height={6}
|
||||
borderRadius={999}
|
||||
@@ -209,9 +211,10 @@ type TimelineProps = {
|
||||
points: TimelinePoint[];
|
||||
formatNumber: (value: number) => string;
|
||||
emptyCopy: string;
|
||||
isDark: boolean;
|
||||
};
|
||||
|
||||
function Timeline({ points, formatNumber, emptyCopy }: TimelineProps) {
|
||||
function Timeline({ points, formatNumber, emptyCopy, isDark }: TimelineProps) {
|
||||
if (points.length === 0) {
|
||||
return (
|
||||
<Text fontSize="$2" color="$color" opacity={0.7}>
|
||||
@@ -229,9 +232,9 @@ function Timeline({ points, formatNumber, emptyCopy }: TimelineProps) {
|
||||
justifyContent="space-between"
|
||||
padding="$2"
|
||||
borderRadius="$card"
|
||||
backgroundColor="rgba(15, 23, 42, 0.05)"
|
||||
backgroundColor={isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.05)'}
|
||||
borderWidth={1}
|
||||
borderColor="rgba(15, 23, 42, 0.08)"
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.16)' : 'rgba(15, 23, 42, 0.08)'}
|
||||
>
|
||||
<Text fontSize="$2" fontWeight="$6">
|
||||
{point.date}
|
||||
@@ -257,6 +260,7 @@ type HighlightsProps = {
|
||||
topPhotoFallbackGuest: string;
|
||||
trendingTitle: string;
|
||||
trendingCountLabel: string;
|
||||
isDark: boolean;
|
||||
};
|
||||
|
||||
function Highlights({
|
||||
@@ -271,6 +275,7 @@ function Highlights({
|
||||
topPhotoFallbackGuest,
|
||||
trendingTitle,
|
||||
trendingCountLabel,
|
||||
isDark,
|
||||
}: HighlightsProps) {
|
||||
if (!topPhoto && !trendingEmotion) {
|
||||
return (
|
||||
@@ -298,7 +303,13 @@ function Highlights({
|
||||
style={{ width: '100%', height: 180, objectFit: 'cover', borderRadius: 16 }}
|
||||
/>
|
||||
) : (
|
||||
<YStack height={180} borderRadius={16} backgroundColor="rgba(15, 23, 42, 0.08)" alignItems="center" justifyContent="center">
|
||||
<YStack
|
||||
height={180}
|
||||
borderRadius={16}
|
||||
backgroundColor={isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(15, 23, 42, 0.08)'}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Text fontSize="$2" color="$color" opacity={0.7}>
|
||||
{topPhotoNoPreview}
|
||||
</Text>
|
||||
@@ -347,9 +358,10 @@ type FeedProps = {
|
||||
emptyCopy: string;
|
||||
guestFallback: string;
|
||||
likesLabel: string;
|
||||
isDark: boolean;
|
||||
};
|
||||
|
||||
function Feed({ feed, formatRelativeTime, locale, formatNumber, emptyCopy, guestFallback, likesLabel }: FeedProps) {
|
||||
function Feed({ feed, formatRelativeTime, locale, formatNumber, emptyCopy, guestFallback, likesLabel, isDark }: FeedProps) {
|
||||
if (feed.length === 0) {
|
||||
return (
|
||||
<Text fontSize="$2" color="$color" opacity={0.7}>
|
||||
@@ -368,9 +380,9 @@ function Feed({ feed, formatRelativeTime, locale, formatNumber, emptyCopy, guest
|
||||
gap="$2"
|
||||
padding="$2"
|
||||
borderRadius="$card"
|
||||
backgroundColor="rgba(15, 23, 42, 0.05)"
|
||||
backgroundColor={isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.05)'}
|
||||
borderWidth={1}
|
||||
borderColor="rgba(15, 23, 42, 0.08)"
|
||||
borderColor={isDark ? 'rgba(255, 255, 255, 0.16)' : 'rgba(15, 23, 42, 0.08)'}
|
||||
>
|
||||
{item.thumbnail ? (
|
||||
<img
|
||||
@@ -379,8 +391,15 @@ function Feed({ feed, formatRelativeTime, locale, formatNumber, emptyCopy, guest
|
||||
style={{ width: 64, height: 64, objectFit: 'cover', borderRadius: 12 }}
|
||||
/>
|
||||
) : (
|
||||
<YStack width={64} height={64} borderRadius={12} backgroundColor="rgba(15, 23, 42, 0.08)" alignItems="center" justifyContent="center">
|
||||
<Camera size={18} color="#0F172A" />
|
||||
<YStack
|
||||
width={64}
|
||||
height={64}
|
||||
borderRadius={12}
|
||||
backgroundColor={isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(15, 23, 42, 0.08)'}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Camera size={18} color={isDark ? '#F8FAFF' : '#0F172A'} />
|
||||
</YStack>
|
||||
)}
|
||||
<YStack flex={1} gap="$1">
|
||||
@@ -415,7 +434,6 @@ export default function AchievementsScreen() {
|
||||
const { locale } = useLocale();
|
||||
const { isDark } = useGuestThemeVariant();
|
||||
const navigate = useNavigate();
|
||||
const surface = getBentoSurfaceTokens(isDark);
|
||||
const mutedButton = isDark ? 'rgba(255, 255, 255, 0.08)' : 'rgba(15, 23, 42, 0.06)';
|
||||
const mutedButtonBorder = isDark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(15, 23, 42, 0.12)';
|
||||
const [payload, setPayload] = React.useState<AchievementsPayload | null>(null);
|
||||
@@ -576,6 +594,7 @@ export default function AchievementsScreen() {
|
||||
badges={personal?.badges ?? []}
|
||||
emptyCopy={t('achievements.badges.empty', 'No badges yet.')}
|
||||
completeCopy={t('achievements.badges.complete', 'Complete')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</YStack>
|
||||
</BentoCard>
|
||||
@@ -597,6 +616,7 @@ export default function AchievementsScreen() {
|
||||
topPhotoFallbackGuest={t('achievements.leaderboard.guestFallback', 'Guest')}
|
||||
trendingTitle={t('achievements.highlights.trendingTitle', 'Trending emotion')}
|
||||
trendingCountLabel={t('achievements.highlights.trendingCount', { count: '{count}' }, '{count} photos')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</BentoCard>
|
||||
|
||||
@@ -613,6 +633,7 @@ export default function AchievementsScreen() {
|
||||
points={highlights?.timeline ?? []}
|
||||
formatNumber={formatNumber}
|
||||
emptyCopy={t('achievements.timeline.empty', 'No timeline data yet.')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</BentoCard>
|
||||
</YStack>
|
||||
@@ -626,6 +647,7 @@ export default function AchievementsScreen() {
|
||||
emptyCopy={t('achievements.leaderboard.uploadsEmpty', 'No uploads yet.')}
|
||||
formatNumber={formatNumber}
|
||||
guestFallback={t('achievements.leaderboard.guestFallback', 'Guest')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</BentoCard>
|
||||
<BentoCard isDark={isDark}>
|
||||
@@ -637,6 +659,7 @@ export default function AchievementsScreen() {
|
||||
emptyCopy={t('achievements.leaderboard.likesEmpty', 'No likes yet.')}
|
||||
formatNumber={formatNumber}
|
||||
guestFallback={t('achievements.leaderboard.guestFallback', 'Guest')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</BentoCard>
|
||||
</YStack>
|
||||
@@ -654,6 +677,7 @@ export default function AchievementsScreen() {
|
||||
emptyCopy={t('achievements.feed.empty', 'The feed is quiet for now.')}
|
||||
guestFallback={t('achievements.leaderboard.guestFallback', 'Guest')}
|
||||
likesLabel={t('achievements.feed.likesLabel', { count: '{count}' }, '{count} likes')}
|
||||
isDark={isDark}
|
||||
/>
|
||||
</BentoCard>
|
||||
) : null}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { useEventData } from '../context/EventDataContext';
|
||||
import { createPhotoShareLink, deletePhoto, fetchGallery, fetchPhoto, likePhoto, unlikePhoto } from '../services/photosApi';
|
||||
import { usePollGalleryDelta } from '../hooks/usePollGalleryDelta';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
|
||||
@@ -7,10 +7,10 @@ import { ArrowLeft, Loader2 } from 'lucide-react';
|
||||
import AppShell from '../components/AppShell';
|
||||
import StandaloneShell from '../components/StandaloneShell';
|
||||
import SurfaceCard from '../components/SurfaceCard';
|
||||
import PullToRefresh from '@/guest/components/PullToRefresh';
|
||||
import { getHelpArticle, type HelpArticleDetail } from '@/guest/services/helpApi';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import PullToRefresh from '@/shared/guest/components/PullToRefresh';
|
||||
import { getHelpArticle, type HelpArticleDetail } from '@/shared/guest/services/helpApi';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import { Loader2, RefreshCcw, Search } from 'lucide-react';
|
||||
import AppShell from '../components/AppShell';
|
||||
import StandaloneShell from '../components/StandaloneShell';
|
||||
import SurfaceCard from '../components/SurfaceCard';
|
||||
import PullToRefresh from '@/guest/components/PullToRefresh';
|
||||
import { getHelpArticles, type HelpArticleSummary } from '@/guest/services/helpApi';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import PullToRefresh from '@/shared/guest/components/PullToRefresh';
|
||||
import { getHelpArticles, type HelpArticleSummary } from '@/shared/guest/services/helpApi';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ import { buildEventPath } from '../lib/routes';
|
||||
import { useStaggeredReveal } from '../lib/useStaggeredReveal';
|
||||
import { usePollStats } from '../hooks/usePollStats';
|
||||
import { fetchGallery } from '../services/photosApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { fetchTasks, type TaskItem } from '../services/tasksApi';
|
||||
import { useGuestTaskProgress } from '@/guest/hooks/useGuestTaskProgress';
|
||||
import { useGuestTaskProgress } from '@/shared/guest/hooks/useGuestTaskProgress';
|
||||
import { fetchEmotions } from '../services/emotionsApi';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
import { useEventBranding } from '@/guest/context/EventBrandingContext';
|
||||
import { useEventBranding } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
|
||||
|
||||
type ActionTileProps = {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Input } from '@tamagui/input';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { Html5Qrcode } from 'html5-qrcode';
|
||||
import { QrCode, ArrowRight } from 'lucide-react';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { fetchEvent } from '../services/eventApi';
|
||||
import { readGuestName } from '../context/GuestIdentityContext';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
|
||||
@@ -3,9 +3,9 @@ import { useParams } from 'react-router-dom';
|
||||
import { YStack, XStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { LegalMarkdown } from '@/guest/components/legal-markdown';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { LegalMarkdown } from '@/shared/guest/components/legal-markdown';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Loader2, Maximize2, Minimize2, Pause, Play } from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useLiveShowState } from '@/guest/hooks/useLiveShowState';
|
||||
import { useLiveShowPlayback } from '@/guest/hooks/useLiveShowPlayback';
|
||||
import LiveShowStage from '@/guest/components/LiveShowStage';
|
||||
import LiveShowBackdrop from '@/guest/components/LiveShowBackdrop';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { prefersReducedMotion } from '@/guest/lib/motion';
|
||||
import { resolveLiveShowEffect } from '@/guest/lib/liveShowEffects';
|
||||
import { useLiveShowState } from '@/shared/guest/hooks/useLiveShowState';
|
||||
import { useLiveShowPlayback } from '@/shared/guest/hooks/useLiveShowPlayback';
|
||||
import LiveShowStage from '@/shared/guest/components/LiveShowStage';
|
||||
import LiveShowBackdrop from '@/shared/guest/components/LiveShowBackdrop';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { prefersReducedMotion } from '@/shared/guest/lib/motion';
|
||||
import { resolveLiveShowEffect } from '@/shared/guest/lib/liveShowEffects';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
|
||||
export default function LiveShowScreen() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { YStack } from '@tamagui/stacks';
|
||||
import { SizableText as Text } from '@tamagui/text';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -11,8 +11,8 @@ import SurfaceCard from '../components/SurfaceCard';
|
||||
import ShareSheet from '../components/ShareSheet';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { fetchGallery, fetchPhoto, likePhoto, createPhotoShareLink } from '../services/photosApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { pushGuestToast } from '../lib/toast';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SizableText as Text } from '@tamagui/text';
|
||||
import { Button } from '@tamagui/button';
|
||||
import { Input } from '@tamagui/input';
|
||||
import { Card } from '@tamagui/card';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { useGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
|
||||
@@ -8,10 +8,10 @@ import { Sheet } from '@tamagui/sheet';
|
||||
import StandaloneShell from '../components/StandaloneShell';
|
||||
import SurfaceCard from '../components/SurfaceCard';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { fetchGalleryMeta, fetchGalleryPhotos, type GalleryMetaResponse, type GalleryPhotoResource } from '@/guest/services/galleryApi';
|
||||
import { createPhotoShareLink } from '@/guest/services/photosApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { EventBrandingProvider } from '@/guest/context/EventBrandingContext';
|
||||
import { fetchGalleryMeta, fetchGalleryPhotos, type GalleryMetaResponse, type GalleryPhotoResource } from '@/shared/guest/services/galleryApi';
|
||||
import { createPhotoShareLink } from '@/shared/guest/services/photosApi';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { EventBrandingProvider } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { mapEventBranding } from '../lib/eventBranding';
|
||||
import { BrandingTheme } from '../lib/brandingTheme';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
@@ -9,7 +9,7 @@ import { buildEventShareLink } from '../services/eventLink';
|
||||
import { usePollStats } from '../hooks/usePollStats';
|
||||
import { fetchEventQrCode } from '../services/qrApi';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
import { pushGuestToast } from '../lib/toast';
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ import { AlertCircle, Download, Maximize2, X } from 'lucide-react';
|
||||
import StandaloneShell from '../components/StandaloneShell';
|
||||
import SurfaceCard from '../components/SurfaceCard';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { fetchPhotoShare } from '@/guest/services/photosApi';
|
||||
import type { EventBrandingPayload } from '@/guest/services/eventApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { EventBrandingProvider } from '@/guest/context/EventBrandingContext';
|
||||
import { fetchPhotoShare } from '@/shared/guest/services/photosApi';
|
||||
import type { EventBrandingPayload } from '@/shared/guest/services/eventApi';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { EventBrandingProvider } from '@/shared/guest/context/EventBrandingContext';
|
||||
import { mapEventBranding } from '../lib/eventBranding';
|
||||
import { BrandingTheme } from '../lib/brandingTheme';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ChevronLeft, ChevronRight, Pause, Play, Maximize2, Minimize2 } from 'lu
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import EventLogo from '../components/EventLogo';
|
||||
import { fetchGallery, type GalleryPhoto } from '../services/photosApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
|
||||
function normalizeImageUrl(src?: string | null) {
|
||||
if (!src) return '';
|
||||
|
||||
@@ -9,7 +9,7 @@ import SurfaceCard from '../components/SurfaceCard';
|
||||
import { fetchTasks, type TaskItem } from '../services/tasksApi';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
|
||||
function getTaskValue(task: TaskItem, key: string): string | undefined {
|
||||
|
||||
@@ -6,13 +6,13 @@ import { Trophy, Play } from 'lucide-react';
|
||||
import AppShell from '../components/AppShell';
|
||||
import TaskHeroCard, { type TaskHero } from '../components/TaskHeroCard';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { fetchTasks } from '../services/tasksApi';
|
||||
import { fetchEmotions } from '../services/emotionsApi';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useGuestTaskProgress } from '@/guest/hooks/useGuestTaskProgress';
|
||||
import { useGuestTaskProgress } from '@/shared/guest/hooks/useGuestTaskProgress';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import { RefreshCcw, Trash2, UploadCloud } from 'lucide-react';
|
||||
import AppShell from '../components/AppShell';
|
||||
import SurfaceCard from '../components/SurfaceCard';
|
||||
import { useUploadQueue } from '../services/uploadApi';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useLocale } from '@/guest/i18n/LocaleContext';
|
||||
import { useLocale } from '@/shared/guest/i18n/LocaleContext';
|
||||
import { useEventData } from '../context/EventDataContext';
|
||||
import { fetchPendingUploadsSummary, type PendingUpload } from '@/guest/services/pendingUploadsApi';
|
||||
import { fetchPendingUploadsSummary, type PendingUpload } from '@/shared/guest/services/pendingUploadsApi';
|
||||
|
||||
type ProgressMap = Record<number, number>;
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ import { useOptionalGuestIdentity } from '../context/GuestIdentityContext';
|
||||
import { uploadPhoto, useUploadQueue } from '../services/uploadApi';
|
||||
import { useGuestThemeVariant } from '../lib/guestTheme';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useTranslation } from '@/guest/i18n/useTranslation';
|
||||
import { useGuestTaskProgress } from '@/guest/hooks/useGuestTaskProgress';
|
||||
import { fetchPendingUploadsSummary, type PendingUpload } from '@/guest/services/pendingUploadsApi';
|
||||
import { resolveUploadErrorDialog, type UploadErrorDialog } from '@/guest/lib/uploadErrorDialog';
|
||||
import { useTranslation } from '@/shared/guest/i18n/useTranslation';
|
||||
import { useGuestTaskProgress } from '@/shared/guest/hooks/useGuestTaskProgress';
|
||||
import { fetchPendingUploadsSummary, type PendingUpload } from '@/shared/guest/services/pendingUploadsApi';
|
||||
import { resolveUploadErrorDialog, type UploadErrorDialog } from '@/shared/guest/lib/uploadErrorDialog';
|
||||
import { fetchTasks, type TaskItem } from '../services/tasksApi';
|
||||
import { pushGuestToast } from '../lib/toast';
|
||||
import { getBentoSurfaceTokens } from '../lib/bento';
|
||||
import { buildEventPath } from '../lib/routes';
|
||||
import { compressPhoto, formatBytes } from '@/guest/lib/image';
|
||||
import { compressPhoto, formatBytes } from '@/shared/guest/lib/image';
|
||||
|
||||
function getTaskValue(task: TaskItem, key: string): string | undefined {
|
||||
const value = task?.[key as keyof TaskItem];
|
||||
|
||||
@@ -3,4 +3,4 @@ export {
|
||||
type AchievementsPayload,
|
||||
type AchievementBadge,
|
||||
type LeaderboardEntry,
|
||||
} from '@/guest/services/achievementApi';
|
||||
} from '@/shared/guest/services/achievementApi';
|
||||
|
||||
@@ -5,4 +5,4 @@ export {
|
||||
type EventStats,
|
||||
FetchEventError,
|
||||
type FetchEventErrorCode,
|
||||
} from '@/guest/services/eventApi';
|
||||
} from '@/shared/guest/services/eventApi';
|
||||
|
||||
@@ -3,4 +3,4 @@ export {
|
||||
fetchGalleryPhotos,
|
||||
type GalleryMetaResponse,
|
||||
type GalleryPhotoResource,
|
||||
} from '@/guest/services/galleryApi';
|
||||
} from '@/shared/guest/services/galleryApi';
|
||||
|
||||
@@ -3,4 +3,4 @@ export {
|
||||
markGuestNotificationRead,
|
||||
dismissGuestNotification,
|
||||
type GuestNotificationItem,
|
||||
} from '@/guest/services/notificationApi';
|
||||
} from '@/shared/guest/services/notificationApi';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fetchJson } from './apiClient';
|
||||
import { getDeviceId } from '../lib/device';
|
||||
export { likePhoto, unlikePhoto, createPhotoShareLink, uploadPhoto, deletePhoto } from '@/guest/services/photosApi';
|
||||
export { likePhoto, unlikePhoto, createPhotoShareLink, uploadPhoto, deletePhoto } from '@/shared/guest/services/photosApi';
|
||||
|
||||
export type GalleryPhoto = Record<string, unknown>;
|
||||
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export { registerGuestPushSubscription, unregisterGuestPushSubscription } from '@/guest/services/pushApi';
|
||||
export {
|
||||
registerPushSubscription as registerGuestPushSubscription,
|
||||
unregisterPushSubscription as unregisterGuestPushSubscription,
|
||||
} from '@/shared/guest/services/pushApi';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { uploadPhoto } from '@/guest/services/photosApi';
|
||||
export { enqueue } from '@/guest/queue/queue';
|
||||
export { useUploadQueue } from '@/guest/queue/hooks';
|
||||
export { uploadPhoto } from '@/shared/guest/services/photosApi';
|
||||
export { enqueue } from '@/shared/guest/queue/queue';
|
||||
export { useUploadQueue } from '@/shared/guest/queue/hooks';
|
||||
|
||||
Reference in New Issue
Block a user