chore(slideshow): renumber migrations to 138/139 (after whatsapp #649's 137)
PR #649 takes migration 137 (add_whatsapp_template_language). Renumber the slideshow migrations to slot in after it: - 137_add_slideshow_share.js -> 138_add_slideshow_share.js - 138_add_slideshow_styling.js -> 139_add_slideshow_styling.js and update the slideshow migration-number references in comments/types. No content change — both are additive + addColumnIfNotExists-guarded, so re-running under the new filename on an already-migrated DB is a safe no-op.
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
const { addColumnIfNotExists } = require('../helpers');
|
const { addColumnIfNotExists } = require('../helpers');
|
||||||
|
|
||||||
// Live Slideshow styling (migration 137 follow-up):
|
// Live Slideshow styling (migration 138 follow-up):
|
||||||
// - a ZDF/ARD-ident-style watermark: a white, semi-transparent logo in a
|
// - a ZDF/ARD-ident-style watermark: a white, semi-transparent logo in a
|
||||||
// corner of the projected slideshow (sourced from the site branding logo
|
// corner of the projected slideshow (sourced from the site branding logo
|
||||||
// or the event's own logo).
|
// or the event's own logo).
|
||||||
@@ -54,5 +54,5 @@ exports.up = async function up(knex) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.down = async function down() {
|
exports.down = async function down() {
|
||||||
// Safe rollback - intentionally no-op to avoid data loss (mirrors 074/137).
|
// Safe rollback - intentionally no-op to avoid data loss (mirrors 074/138).
|
||||||
};
|
};
|
||||||
@@ -1882,7 +1882,7 @@ router.post('/:id/toggle-status', adminAuth, requirePermission('events.edit'), r
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Live Slideshow ("Diashow") — a token-only fullscreen kiosk link for live
|
// Live Slideshow ("Diashow") — a token-only fullscreen kiosk link for live
|
||||||
// events that auto-picks-up new uploads (migration 137). Mirrors the
|
// events that auto-picks-up new uploads (migration 138). Mirrors the
|
||||||
// client-access second-token pattern: the link is minted on demand, rotatable
|
// client-access second-token pattern: the link is minted on demand, rotatable
|
||||||
// and disable-able, independent of the gallery password / share link.
|
// and disable-able, independent of the gallery password / share link.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ router.put('/accounting', adminAuth, requirePermission('settings.edit'), async (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global Live Slideshow defaults (migration 138). The per-event watermark is
|
// Global Live Slideshow defaults (migration 139). The per-event watermark is
|
||||||
// tri-state (events.show_watermark NULL = inherit these). Read via the generic
|
// tri-state (events.show_watermark NULL = inherit these). Read via the generic
|
||||||
// GET /:type ('slideshow'); this is the typed write.
|
// GET /:type ('slideshow'); this is the typed write.
|
||||||
router.put('/slideshow', adminAuth, requirePermission('settings.edit'), async (req, res) => {
|
router.put('/slideshow', adminAuth, requirePermission('settings.edit'), async (req, res) => {
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ router.get('/:slug/info', async (req, res) => {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Live Slideshow ("Diashow") — token-only fullscreen kiosk surface
|
// Live Slideshow ("Diashow") — token-only fullscreen kiosk surface
|
||||||
// (migration 137). The token in the URL IS the secret (no gallery password),
|
// (migration 138). The token in the URL IS the secret (no gallery password),
|
||||||
// so these routes are unauthenticated except for the token match itself. The
|
// so these routes are unauthenticated except for the token match itself. The
|
||||||
// slideshow shows ALL public/visible, finished photos — exactly the guest
|
// slideshow shows ALL public/visible, finished photos — exactly the guest
|
||||||
// set — so once /session mints a short-lived `accessLevel:'slideshow'` JWT,
|
// set — so once /session mints a short-lived `accessLevel:'slideshow'` JWT,
|
||||||
@@ -586,7 +586,7 @@ router.get('/:slug/photos', verifyGalleryAccess, resolveGuest, async (req, res)
|
|||||||
// Log view — but NOT for the Live Slideshow kiosk. A running projector
|
// Log view — but NOT for the Live Slideshow kiosk. A running projector
|
||||||
// refetches this list on every new-upload poll, which would massively
|
// refetches this list on every new-upload poll, which would massively
|
||||||
// inflate total_views / unique_visitors. The slideshow is explicitly
|
// inflate total_views / unique_visitors. The slideshow is explicitly
|
||||||
// excluded from real visitor analytics (migration 137 design).
|
// excluded from real visitor analytics (migration 138 design).
|
||||||
if (req.accessLevel !== 'slideshow') {
|
if (req.accessLevel !== 'slideshow') {
|
||||||
await db('access_logs').insert({
|
await db('access_logs').insert({
|
||||||
event_id: req.event.id,
|
event_id: req.event.id,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* <SlideshowSettingsCard>
|
* <SlideshowSettingsCard>
|
||||||
*
|
*
|
||||||
* Per-event "Live Slideshow" ("Diashow") admin surface (migrations 137/138).
|
* Per-event "Live Slideshow" ("Diashow") admin surface (migrations 138/139).
|
||||||
* A token-only fullscreen kiosk link for live events that auto-picks-up new
|
* A token-only fullscreen kiosk link for live events that auto-picks-up new
|
||||||
* uploads while it runs. Mounted once on the EventDetailsPage; admin can:
|
* uploads while it runs. Mounted once on the EventDetailsPage; admin can:
|
||||||
* - Generate the slideshow link on demand (mints show_share_token)
|
* - Generate the slideshow link on demand (mints show_share_token)
|
||||||
|
|||||||
@@ -2159,7 +2159,7 @@ export const EventDetailsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Live Slideshow ("Diashow") link + live display settings (migrations 137/138).
|
{/* Live Slideshow ("Diashow") link + live display settings (migrations 138/139).
|
||||||
Gated behind the `slideshow` feature flag. */}
|
Gated behind the `slideshow` feature flag. */}
|
||||||
{flags.slideshow && (
|
{flags.slideshow && (
|
||||||
<SlideshowSettingsCard
|
<SlideshowSettingsCard
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { GALLERY_THEME_PRESETS } from '../../../types/theme.types';
|
||||||
|
|
||||||
|
const CSS = '.gallery-premium-footer p:first-child { display: none; }';
|
||||||
|
const savedTheme = { ...GALLERY_THEME_PRESETS.galleryPremium.config, customCss: CSS };
|
||||||
|
|
||||||
|
// --- mocks -----------------------------------------------------------------
|
||||||
|
vi.mock('react-i18next', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useTranslation: () => ({ t: (k: string, fb?: unknown) => (typeof fb === 'string' ? fb : k) })
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('react-toastify', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));
|
||||||
|
|
||||||
|
vi.mock('../../../services/settings.service', async () => {
|
||||||
|
const actual = await vi.importActual<any>('../../../services/settings.service');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
settingsService: {
|
||||||
|
...actual.settingsService,
|
||||||
|
getSettingsByType: vi.fn(async (type: string) =>
|
||||||
|
type === 'theme' ? { theme_config: savedTheme } : {}
|
||||||
|
),
|
||||||
|
getAllSettings: vi.fn(async () => ({ thumbnail_width: '800', thumbnail_height: '800' })),
|
||||||
|
updateTheme: vi.fn(async () => undefined),
|
||||||
|
updateBranding: vi.fn(async () => undefined),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../../../services/fonts.service', () => ({
|
||||||
|
fontsService: { list: vi.fn(async () => []) },
|
||||||
|
extractFamilyName: (s: string) => s,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../services/businessProfile.service', () => ({
|
||||||
|
businessProfileService: { get: vi.fn(async () => ({ profile: {} })), update: vi.fn() },
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/usePublicSettings', () => ({
|
||||||
|
PUBLIC_SETTINGS_QUERY_KEY: ['public-settings'],
|
||||||
|
usePublicSettings: () => ({ data: { branding_force_color_mode: null } }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../contexts/FeatureFlagsContext', () => ({
|
||||||
|
useFeatureFlags: () => ({ flags: {} }),
|
||||||
|
useFeatureEnabled: () => false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Heavy/unrelated children — stub to keep the test focused on the CSS textarea.
|
||||||
|
vi.mock('../../../components/admin/CustomerDashboardBrandingCard', () => ({
|
||||||
|
CustomerDashboardBrandingCard: () => null,
|
||||||
|
}));
|
||||||
|
vi.mock('../../../components/admin/PdfTypographyCard', () => ({
|
||||||
|
PdfTypographyCard: () => null,
|
||||||
|
}));
|
||||||
|
vi.mock('../../../components/admin', async () => {
|
||||||
|
const actual = await vi.importActual<any>('../../../components/admin');
|
||||||
|
return { ...actual, GalleryPreview: () => null };
|
||||||
|
});
|
||||||
|
|
||||||
|
import { BrandingPage } from '../BrandingPage';
|
||||||
|
import { ThemeProvider } from '../../../contexts/ThemeContext';
|
||||||
|
|
||||||
|
describe('BrandingPage custom CSS persistence (#645)', () => {
|
||||||
|
beforeEach(() => localStorage.clear());
|
||||||
|
|
||||||
|
it('shows the saved custom CSS in the textarea on load for a Gallery Premium theme', async () => {
|
||||||
|
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||||
|
render(
|
||||||
|
<QueryClientProvider client={qc}>
|
||||||
|
<ThemeProvider>
|
||||||
|
<BrandingPage />
|
||||||
|
</ThemeProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const textarea = (await screen.findByPlaceholderText(
|
||||||
|
'/* Add custom CSS here */'
|
||||||
|
)) as HTMLTextAreaElement;
|
||||||
|
|
||||||
|
await waitFor(() => expect(textarea.value).toBe(CSS));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -67,7 +67,7 @@ export interface Event {
|
|||||||
// Client access (#172)
|
// Client access (#172)
|
||||||
client_access_enabled?: boolean;
|
client_access_enabled?: boolean;
|
||||||
client_share_token?: string;
|
client_share_token?: string;
|
||||||
// Live Slideshow / "Diashow" (migration 137). Token-only fullscreen kiosk
|
// Live Slideshow / "Diashow" (migration 138). Token-only fullscreen kiosk
|
||||||
// link minted on demand; null token = disabled. Settings drive the running
|
// link minted on demand; null token = disabled. Settings drive the running
|
||||||
// projector and can be changed live.
|
// projector and can be changed live.
|
||||||
show_share_token?: string | null;
|
show_share_token?: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user