typescript-typenfehler behoben.. npm run lint läuft nun fehlerfrei durch.
This commit is contained in:
@@ -3,7 +3,7 @@ import { ApiError, emitApiErrorEvent } from './lib/apiError';
|
||||
import type { EventLimitSummary } from './lib/limitWarnings';
|
||||
import i18n from './i18n';
|
||||
|
||||
type JsonValue = Record<string, any>;
|
||||
type JsonValue = Record<string, unknown>;
|
||||
|
||||
export type TenantAccountProfile = {
|
||||
id: number;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StatItem {
|
||||
key: string;
|
||||
|
||||
@@ -44,7 +44,7 @@ export function EventProvider({ children }: { children: React.ReactNode }) {
|
||||
initialData: [],
|
||||
});
|
||||
|
||||
const events = authReady ? fetchedEvents : [];
|
||||
const events = React.useMemo(() => (authReady ? fetchedEvents : []), [authReady, fetchedEvents]);
|
||||
const isLoading = authReady ? queryLoading : status === 'loading';
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -148,9 +148,12 @@ export default function WelcomeOrderSummaryPage() {
|
||||
const packagesState = useTenantPackages();
|
||||
const { t, i18n } = useTranslation("onboarding");
|
||||
const locale = i18n.language?.startsWith("en") ? "en-GB" : "de-DE";
|
||||
const { currencyFormatter, dateFormatter } = useLocaleFormats(locale);
|
||||
const { currencyFormatter } = useLocaleFormats(locale);
|
||||
|
||||
const packageIdFromState = typeof location.state === "object" ? (location.state as any)?.packageId : undefined;
|
||||
const packageIdFromState =
|
||||
typeof location.state === "object" && location.state !== null && "packageId" in location.state
|
||||
? (location.state as { packageId?: number | string | null }).packageId
|
||||
: undefined;
|
||||
const selectedPackageId = progress.selectedPackage?.id ?? packageIdFromState ?? null;
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -6,10 +6,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
TenantHeroCard,
|
||||
TenantOnboardingChecklistCard,
|
||||
FrostedSurface,
|
||||
tenantHeroPrimaryButtonClass,
|
||||
tenantHeroSecondaryButtonClass,
|
||||
|
||||
@@ -327,7 +327,7 @@ const shownWarningToasts = React.useRef<Set<string>>(new Set());
|
||||
scope={warning.scope as 'photos' | 'guests' | 'gallery'}
|
||||
onCheckout={(key) => { void handleAddonPurchase(warning.scope as 'photos' | 'guests' | 'gallery', key); }}
|
||||
busy={addonBusyId === warning.scope}
|
||||
t={(key, fallback) => t(key as any, fallback)}
|
||||
t={(key, fallback) => t(key, fallback)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -452,8 +452,8 @@ export default function EventInvitesPage(): React.ReactElement {
|
||||
const exportLogo = effectiveCustomization?.logo_data_url ?? effectiveCustomization?.logo_url ?? null;
|
||||
const exportQr = selectedInvite?.qr_code_data_url ?? null;
|
||||
|
||||
const handlePreviewSelect = React.useCallback((_id: string | null) => undefined, []);
|
||||
const handlePreviewChange = React.useCallback((_id: string, _patch: Partial<LayoutElement>) => undefined, []);
|
||||
const handlePreviewSelect = React.useCallback(() => undefined, []);
|
||||
const handlePreviewChange = React.useCallback(() => undefined, []);
|
||||
|
||||
const handleCustomizerDraftChange = React.useCallback((draft: QrLayoutCustomization | null) => {
|
||||
setCustomizerDraft((previous) => {
|
||||
@@ -865,7 +865,7 @@ export default function EventInvitesPage(): React.ReactElement {
|
||||
scope="guests"
|
||||
onCheckout={(key) => { void handleAddonPurchase(key); }}
|
||||
busy={addonBusy === 'guests'}
|
||||
t={(key, fallback) => t(key as any, fallback)}
|
||||
t={(key, fallback) => t(key, fallback)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -882,7 +882,7 @@ export default function EventInvitesPage(): React.ReactElement {
|
||||
<CardDescription>{t('events.sections.addons.description', 'Zusätzliche Kontingente für dieses Event.')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AddonSummaryList addons={state.event.addons} t={(key, fallback) => t(key as any, fallback)} />
|
||||
<AddonSummaryList addons={state.event.addons} t={(key, fallback) => t(key, fallback)} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
@@ -13,14 +13,14 @@ import { getAddonCatalog, getEvent, type EventAddonCatalogItem, type EventAddonS
|
||||
import { AdminLayout } from '../components/AdminLayout';
|
||||
import { createEventAddonCheckout, deletePhoto, featurePhoto, getEventPhotos, TenantPhoto, unfeaturePhoto } from '../api';
|
||||
import { isAuthError } from '../auth/tokens';
|
||||
import { getApiErrorMessage, isApiError } from '../lib/apiError';
|
||||
import { getApiErrorMessage } from '../lib/apiError';
|
||||
import { buildLimitWarnings, type EventLimitSummary } from '../lib/limitWarnings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ADMIN_EVENTS_PATH, ADMIN_EVENT_VIEW_PATH } from '../constants';
|
||||
|
||||
export default function EventPhotosPage() {
|
||||
const params = useParams<{ slug?: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const slug = params.slug ?? searchParams.get('slug') ?? null;
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('management');
|
||||
@@ -36,8 +36,6 @@ export default function EventPhotosPage() {
|
||||
const [busyId, setBusyId] = React.useState<number | null>(null);
|
||||
const [limits, setLimits] = React.useState<EventLimitSummary | null>(null);
|
||||
const [addons, setAddons] = React.useState<EventAddonCatalogItem[]>([]);
|
||||
const [catalogError, setCatalogError] = React.useState<string | undefined>(undefined);
|
||||
//const [searchParams, setSearchParams] = React.useState(() => new URLSearchParams(window.location.search));
|
||||
const [eventAddons, setEventAddons] = React.useState<EventAddonSummary[]>([]);
|
||||
|
||||
const load = React.useCallback(async () => {
|
||||
@@ -57,7 +55,6 @@ export default function EventPhotosPage() {
|
||||
setLimits(photoResult.limits ?? null);
|
||||
setEventAddons(eventData.addons ?? []);
|
||||
setAddons(catalog);
|
||||
setCatalogError(undefined);
|
||||
} catch (err) {
|
||||
if (!isAuthError(err)) {
|
||||
setError(getApiErrorMessage(err, 'Fotos konnten nicht geladen werden.'));
|
||||
@@ -81,7 +78,7 @@ export default function EventPhotosPage() {
|
||||
setSearchParams(params);
|
||||
navigate(window.location.pathname, { replace: true });
|
||||
}
|
||||
}, [searchParams, slug, load, navigate, translateLimits]);
|
||||
}, [searchParams, slug, load, navigate, translateLimits, setSearchParams]);
|
||||
|
||||
async function handleToggleFeature(photo: TenantPhoto) {
|
||||
if (!slug) return;
|
||||
@@ -162,7 +159,7 @@ export default function EventPhotosPage() {
|
||||
<CardDescription>{t('events.sections.addons.description', 'Zusätzliche Kontingente für dieses Event.')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AddonSummaryList addons={eventAddons} t={(key, fallback) => t(key as any, fallback)} />
|
||||
<AddonSummaryList addons={eventAddons} t={(key, fallback) => t(key, fallback)} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
ADMIN_EVENT_INVITES_PATH,
|
||||
ADMIN_EVENT_TOOLKIT_PATH,
|
||||
} from '../constants';
|
||||
import { buildLimitWarnings, type EventLimitSummary } from '../lib/limitWarnings';
|
||||
import { buildLimitWarnings } from '../lib/limitWarnings';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function EventsPage() {
|
||||
@@ -57,7 +57,7 @@ export default function EventsPage() {
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const translateManagement = React.useCallback(
|
||||
(key: string, fallback?: string, options?: Record<string, unknown>) =>
|
||||
|
||||
@@ -239,7 +239,7 @@ function formatDate(value: string, language: string | undefined): string {
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Label } from '@/components/ui/label';
|
||||
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ADMIN_DEFAULT_AFTER_LOGIN_PATH, ADMIN_EVENTS_PATH } from '../constants';
|
||||
import { encodeReturnTo, resolveReturnTarget } from '../lib/returnTo';
|
||||
import { resolveReturnTarget } from '../lib/returnTo';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
type LoginResponse = {
|
||||
|
||||
@@ -352,7 +352,6 @@ export function TasksSection({ embedded = false, onNavigateToCollections }: Task
|
||||
|
||||
export default function TasksPage() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation('management');
|
||||
const { t: tc } = useTranslation('common');
|
||||
return (
|
||||
<AdminLayout
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
SunMedium,
|
||||
Users,
|
||||
Wand2,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1561,37 +1561,34 @@ export function InviteLayoutCustomizerPanel({
|
||||
|
||||
const highlightedElementId = activeElementId ?? inspectorElementId;
|
||||
|
||||
const renderResponsiveSection = React.useCallback(
|
||||
(id: string, title: string, description: string, content: React.ReactNode) => {
|
||||
const body = <div className="space-y-4">{content}</div>;
|
||||
|
||||
if (!isCompact) {
|
||||
return (
|
||||
<section key={id} className="space-y-4 rounded-2xl border border-border bg-[var(--tenant-surface)] p-5 shadow-sm transition-colors">
|
||||
<header className="space-y-1">
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
|
||||
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
||||
</header>
|
||||
{body}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
const renderResponsiveSection = (id: string, title: string, description: string, content: React.ReactNode) => {
|
||||
const body = <div className="space-y-4">{content}</div>;
|
||||
|
||||
if (!isCompact) {
|
||||
return (
|
||||
<Collapsible key={id} defaultOpen className="rounded-2xl border border-border bg-[var(--tenant-surface)] p-3 shadow-sm transition-colors">
|
||||
<CollapsibleTrigger type="button" className="flex w-full items-center justify-between gap-3 text-left">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
|
||||
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pt-4">{body}</CollapsibleContent>
|
||||
</Collapsible>
|
||||
<section key={id} className="space-y-4 rounded-2xl border border-border bg-[var(--tenant-surface)] p-5 shadow-sm transition-colors">
|
||||
<header className="space-y-1">
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
|
||||
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
||||
</header>
|
||||
{body}
|
||||
</section>
|
||||
);
|
||||
},
|
||||
[isCompact]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapsible key={id} defaultOpen className="rounded-2xl border border-border bg-[var(--tenant-surface)] p-3 shadow-sm transition-colors">
|
||||
<CollapsibleTrigger type="button" className="flex w-full items-center justify-between gap-3 text-left">
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">{title}</h3>
|
||||
{description ? <p className="text-xs text-muted-foreground">{description}</p> : null}
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform data-[state=open]:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="pt-4">{body}</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
CANVAS_WIDTH,
|
||||
LayoutElement,
|
||||
clamp,
|
||||
LayoutElementType,
|
||||
} from './schema';
|
||||
|
||||
type DesignerCanvasProps = {
|
||||
@@ -23,7 +22,6 @@ type DesignerCanvasProps = {
|
||||
qrCodeDataUrl: string | null;
|
||||
logoDataUrl: string | null;
|
||||
scale?: number;
|
||||
layoutKey?: string;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
@@ -43,7 +41,6 @@ export function DesignerCanvas({
|
||||
qrCodeDataUrl,
|
||||
logoDataUrl,
|
||||
scale = 1,
|
||||
layoutKey,
|
||||
readOnly = false,
|
||||
}: DesignerCanvasProps): React.JSX.Element {
|
||||
const canvasElementRef = React.useRef<HTMLCanvasElement | null>(null);
|
||||
@@ -170,10 +167,6 @@ export function DesignerCanvas({
|
||||
|
||||
return () => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
if (disposeTokenRef.current !== disposeToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
destroyCanvas(canvas);
|
||||
pendingTimeoutRef.current = null;
|
||||
pendingDisposeRef.current = null;
|
||||
@@ -216,8 +209,8 @@ export function DesignerCanvas({
|
||||
onSelect(active.elementId);
|
||||
};
|
||||
|
||||
const handleSelectionCleared = (event?: unknown) => {
|
||||
const pointerEvent = event as { e?: MouseEvent } | undefined;
|
||||
const handleSelectionCleared = (event?: fabric.IEvent<MouseEvent>) => {
|
||||
const pointerEvent = event?.e;
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -229,11 +222,11 @@ export function DesignerCanvas({
|
||||
onSelect(null);
|
||||
};
|
||||
|
||||
const handleObjectModified = (e: any) => {
|
||||
const handleObjectModified = (event: fabric.IEvent<MouseEvent>) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
const target = e.target as FabricObjectWithId | undefined;
|
||||
const target = event.target as FabricObjectWithId | undefined;
|
||||
if (!target || typeof target.elementId !== 'string') {
|
||||
return;
|
||||
}
|
||||
@@ -312,7 +305,7 @@ export function DesignerCanvas({
|
||||
canvas.on('selection:cleared', handleSelectionCleared);
|
||||
canvas.on('object:modified', handleObjectModified);
|
||||
|
||||
const handleEditingExited = (event: { target?: FabricObjectWithId & { text?: string } }) => {
|
||||
const handleEditingExited = (event: fabric.IEvent<MouseEvent> & { target?: FabricObjectWithId & { text?: string } }) => {
|
||||
if (readOnly) {
|
||||
return;
|
||||
}
|
||||
@@ -327,14 +320,14 @@ export function DesignerCanvas({
|
||||
canvas.requestRenderAll();
|
||||
};
|
||||
|
||||
(canvas as any).on('editing:exited', handleEditingExited);
|
||||
canvas.on('editing:exited', handleEditingExited);
|
||||
|
||||
return () => {
|
||||
canvas.off('selection:created', handleSelection);
|
||||
canvas.off('selection:updated', handleSelection);
|
||||
canvas.off('selection:cleared', handleSelectionCleared);
|
||||
canvas.off('object:modified', handleObjectModified);
|
||||
(canvas as any).off('editing:exited', handleEditingExited);
|
||||
canvas.off('editing:exited', handleEditingExited);
|
||||
};
|
||||
}, [onChange, onSelect, readOnly]);
|
||||
|
||||
@@ -702,7 +695,9 @@ export async function createFabricObject({
|
||||
padding: 0, // No padding to fix large frame
|
||||
});
|
||||
if (qrImage) {
|
||||
(qrImage as any).uniformScaling = true; // Lock aspect ratio
|
||||
if (qrImage instanceof fabric.Image) {
|
||||
qrImage.uniformScaling = true; // Lock aspect ratio
|
||||
}
|
||||
qrImage.lockScalingFlip = true;
|
||||
qrImage.padding = 0;
|
||||
qrImage.cornerColor = 'transparent';
|
||||
@@ -801,7 +796,7 @@ export async function loadImageObject(
|
||||
options?: { objectFit?: 'contain' | 'cover'; shadow?: string; padding?: number },
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<fabric.Object | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve) => {
|
||||
let resolved = false;
|
||||
const resolveSafely = (value: fabric.Object | null) => {
|
||||
if (resolved) {
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
// import type { EventQrInviteLayout } from '../../api'; // Temporär deaktiviert wegen Modul-Fehler; definiere lokal falls nötig
|
||||
type EventQrInviteLayout = any; // Placeholder für Typ, bis Pfad gefixt
|
||||
// import type { EventQrInviteLayout } from '../../api'; // Temporär deaktiviert wegen Modul-Fehler; definiere lokal falls nötig
|
||||
type EventQrInviteLayout = {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
subtitle?: string | null;
|
||||
preview?: {
|
||||
background?: string | null;
|
||||
background_gradient?: { angle?: number; stops?: string[] } | null;
|
||||
accent?: string | null;
|
||||
text?: string | null;
|
||||
qr_size_px?: number | null;
|
||||
} | null;
|
||||
formats?: string[];
|
||||
};
|
||||
|
||||
export const CANVAS_WIDTH = 1240;
|
||||
export const CANVAS_HEIGHT = 1754;
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
ADMIN_BASE_PATH,
|
||||
ADMIN_DEFAULT_AFTER_LOGIN_PATH,
|
||||
ADMIN_EVENTS_PATH,
|
||||
ADMIN_HOME_PATH,
|
||||
ADMIN_LOGIN_PATH,
|
||||
ADMIN_LOGIN_START_PATH,
|
||||
ADMIN_PUBLIC_LANDING_PATH,
|
||||
} from './constants';
|
||||
|
||||
Reference in New Issue
Block a user