typescript-typenfehler behoben.. npm run lint läuft nun fehlerfrei durch.

This commit is contained in:
Codex Agent
2025-11-22 11:49:47 +01:00
parent 6c78d7e281
commit eb41cb6194
74 changed files with 469 additions and 396 deletions

View File

@@ -133,7 +133,7 @@ export default function ProfileIndex() {
try {
return dateFormatter.format(new Date(userData.emailVerifiedAt));
} catch (error) {
} catch {
return null;
}
}, [userData.emailVerifiedAt, dateFormatter]);

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import { usePage } from "@inertiajs/react";
import { useTranslation } from "react-i18next";
import toast from "react-hot-toast";
@@ -31,8 +31,6 @@ type SharedPageProps = {
type FieldErrors = Record<string, string>;
const fallbackRoute = (locale: string) => `/${locale}/login`;
const csrfToken = () => (document.querySelector('meta[name="csrf-token"]') as HTMLMetaElement | null)?.content ?? "";
export default function LoginForm({ onSuccess, canResetPassword = true, locale }: LoginFormProps) {

View File

@@ -6,10 +6,8 @@ import { LoaderCircle, User, Mail, Phone, Lock, MapPin } from 'lucide-react';
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import type { GoogleProfilePrefill } from '../marketing/checkout/types';
declare const route: (name: string, params?: Record<string, unknown>) => string;
export interface RegisterSuccessPayload {
user: any | null;
user: unknown | null;
redirect?: string | null;
pending_purchase?: boolean;
}
@@ -69,7 +67,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
const [serverError, setServerError] = useState<string | null>(null);
const [serverErrorType, setServerErrorType] = useState<'generic' | 'session-expired'>('generic');
const { t } = useTranslation(['auth', 'common']);
const page = usePage<{ errors: Record<string, string>; locale?: string; auth?: { user?: any | null } }>();
const page = usePage<{ errors: Record<string, string>; locale?: string; auth?: { user?: unknown | null } }>();
const resolvedLocale = locale ?? page.props.locale ?? 'de';
const { data, setData, errors, clearErrors, reset, setError } = useForm<RegisterFormFields>({
@@ -245,7 +243,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
try {
const json = await response.clone().json();
message = json?.message ?? null;
} catch (error) {
} catch {
message = null;
}

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useForm } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import { LoaderCircle, User, Mail, Phone, Lock, Home, MapPin } from 'lucide-react';
import { LoaderCircle, User, Mail, Phone, Lock, MapPin } from 'lucide-react';
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog';
interface RegisterProps {

View File

@@ -110,7 +110,10 @@ export default function Dashboard() {
const page = usePage<SharedData & DashboardPageProps>();
const { metrics, upcomingEvents, recentPurchases, latestPurchase, tenant, emailVerification, locale, onboarding } = page.props;
const { auth, supportedLocales } = page.props;
const translations = (page.props.translations?.dashboard ?? {}) as Record<string, unknown>;
const translations = useMemo(
() => (page.props.translations?.dashboard ?? {}) as Record<string, unknown>,
[page.props.translations?.dashboard],
);
const [verificationSent, setVerificationSent] = useState(false);
const [sendingVerification, setSendingVerification] = useState(false);
@@ -285,7 +288,7 @@ export default function Dashboard() {
try {
return currencyFormatter.format(price);
} catch (error) {
} catch {
return `${price.toFixed(2)}`;
}
};
@@ -669,4 +672,4 @@ export default function Dashboard() {
);
}
(Dashboard as any).layout = (page: ReactNode) => page;
Object.assign(Dashboard, { layout: (page: ReactNode) => page });

View File

@@ -50,7 +50,10 @@ const MarkdownPreview: React.FC<{ html?: string; fallback?: string; className?:
const Blog: React.FC<Props> = ({ posts }) => {
const { localizedPath } = useLocalizedRoutes();
const { props } = usePage<{ supportedLocales?: string[] }>();
const supportedLocales = props.supportedLocales && props.supportedLocales.length > 0 ? props.supportedLocales : ['de', 'en'];
const supportedLocales = useMemo(
() => (props.supportedLocales && props.supportedLocales.length > 0 ? props.supportedLocales : ['de', 'en']),
[props.supportedLocales]
);
const { t, i18n } = useTranslation('marketing');
const locale = i18n.language || 'de';
const articles = posts?.data ?? [];
@@ -116,7 +119,7 @@ const Blog: React.FC<Props> = ({ posts }) => {
return localizedPath(raw);
}
},
[localizedPath]
[localizedPath, supportedLocales]
);
const renderPagination = () => {

View File

@@ -11,7 +11,7 @@ const Kontakt: React.FC = () => {
message: '',
});
const { flash } = usePage().props as any;
const { flash } = usePage<{ flash?: { success?: string } }>().props;
const { t } = useTranslation('marketing');
const { localizedPath } = useLocalizedRoutes();

View File

@@ -1,22 +1,20 @@
import React, { useState, useEffect, useMemo, useRef, useLayoutEffect } from 'react';
import { Head, Link, usePage } from '@inertiajs/react';
import { Link } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Sheet, SheetContent } from '@/components/ui/sheet';
import { Separator } from '@/components/ui/separator';
import { cn } from '@/lib/utils';
import MarketingLayout from '@/layouts/mainWebsite';
import { useAnalytics } from '@/hooks/useAnalytics';
import { useCtaExperiment } from '@/hooks/useCtaExperiment';
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { ArrowRight, ShoppingCart, Check, Users, Image, Shield, Star, Sparkles } from 'lucide-react';
import { ArrowRight, Check, Shield, Star, Sparkles } from 'lucide-react';
interface Package {
id: number;
@@ -214,14 +212,11 @@ interface PackagesProps {
const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackages }) => {
const [open, setOpen] = useState(false);
const [selectedPackage, setSelectedPackage] = useState<Package | null>(null);
const [currentStep, setCurrentStep] = useState<'overview' | 'testimonials'>('overview');
const [isMobile, setIsMobile] = useState(false);
const dialogScrollRef = useRef<HTMLDivElement | null>(null);
const dialogHeadingRef = useRef<HTMLDivElement | null>(null);
const mobileEndcustomerRef = useRef<HTMLDivElement | null>(null);
const mobileResellerRef = useRef<HTMLDivElement | null>(null);
const { props } = usePage();
const { auth } = props as any;
const { localizedPath } = useLocalizedRoutes();
const { t } = useTranslation('marketing');
const { t: tCommon } = useTranslation('common');
@@ -278,10 +273,18 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
const media = window.matchMedia('(max-width: 768px)');
const update = () => setIsMobile(media.matches);
update();
media.addEventListener ? media.addEventListener('change', update) : media.addListener(update);
if (media.addEventListener) {
media.addEventListener('change', update);
} else {
media.addListener(update);
}
return () => {
media.removeEventListener ? media.removeEventListener('change', update) : media.removeListener(update);
if (media.removeEventListener) {
media.removeEventListener('change', update);
} else {
media.removeListener(update);
}
};
}, []);
@@ -403,7 +406,6 @@ function selectHighlightPackageId(packages: Package[]): number | null {
value: pkg.price,
});
setSelectedPackage(pkg);
setCurrentStep('overview');
setOpen(true);
};
@@ -418,20 +420,6 @@ function selectHighlightPackageId(packages: Package[]): number | null {
// nextStep entfernt, da Tabs nun parallel sind
const getFeatureIcon = (feature: string) => {
switch (feature) {
case 'basic_uploads': return <Image className="w-4 h-4" />;
case 'unlimited_sharing': return <ArrowRight className="w-4 h-4" />;
case 'no_watermark': return <Shield className="w-4 h-4" />;
case 'custom_tasks': return <Check className="w-4 h-4" />;
case 'advanced_analytics': return <Star className="w-4 h-4" />;
case 'priority_support': return <Users className="w-4 h-4" />;
case 'reseller_dashboard': return <ShoppingCart className="w-4 h-4" />;
case 'custom_branding': return <Image className="w-4 h-4" />;
default: return <Check className="w-4 h-4" />;
}
};
const getAccentTheme = (variant: 'endcustomer' | 'reseller') =>
variant === 'reseller'
? {

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { usePage, router } from '@inertiajs/react';
import { Head } from '@inertiajs/react';
import { useTranslation } from 'react-i18next';
import MarketingLayout from '@/layouts/mainWebsite';
import { Loader } from 'lucide-react';
@@ -8,7 +7,7 @@ import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
import { ADMIN_HOME_PATH } from '@/admin/constants';
const Success: React.FC = () => {
const { auth } = usePage().props as any;
const { auth } = usePage<{ auth: { user?: { email_verified_at?: string | null } } }>().props;
const { t } = useTranslation('success');
const { localizedPath } = useLocalizedRoutes();

View File

@@ -104,7 +104,10 @@ const WizardBody: React.FC<{
[t]
);
const currentIndex = useMemo(() => stepConfig.findIndex((step) => step.id === currentStep), [currentStep]);
const currentIndex = useMemo(
() => stepConfig.findIndex((step) => step.id === currentStep),
[currentStep, stepConfig]
);
const progress = useMemo(() => {
if (currentIndex < 0) {
return 0;

View File

@@ -5,7 +5,7 @@ interface CheckoutState {
currentStep: CheckoutStepId;
selectedPackage: CheckoutPackage | null;
packageOptions: CheckoutPackage[];
authUser: any;
authUser: unknown;
isAuthenticated: boolean;
paymentIntent: string | null;
loading: boolean;
@@ -19,7 +19,7 @@ interface CheckoutWizardContextType {
packageOptions: CheckoutPackage[];
currentStep: CheckoutStepId;
isAuthenticated: boolean;
authUser: any;
authUser: unknown;
paddleConfig?: {
environment?: string | null;
client_token?: string | null;
@@ -27,7 +27,7 @@ interface CheckoutWizardContextType {
paymentCompleted: boolean;
selectPackage: (pkg: CheckoutPackage) => void;
setSelectedPackage: (pkg: CheckoutPackage) => void;
setAuthUser: (user: any) => void;
setAuthUser: (user: unknown) => void;
nextStep: () => void;
prevStep: () => void;
previousStep: () => void;
@@ -56,7 +56,7 @@ const initialState: CheckoutState = {
type CheckoutAction =
| { type: 'SELECT_PACKAGE'; payload: CheckoutPackage }
| { type: 'SET_AUTH_USER'; payload: any }
| { type: 'SET_AUTH_USER'; payload: unknown }
| { type: 'NEXT_STEP' }
| { type: 'PREV_STEP' }
| { type: 'GO_TO_STEP'; payload: CheckoutStepId }
@@ -72,19 +72,23 @@ function checkoutReducer(state: CheckoutState, action: CheckoutAction): Checkout
case 'SET_AUTH_USER':
return { ...state, authUser: action.payload, isAuthenticated: Boolean(action.payload) };
case 'NEXT_STEP':
const steps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation'];
const currentIndex = steps.indexOf(state.currentStep);
if (currentIndex < steps.length - 1) {
return { ...state, currentStep: steps[currentIndex + 1] };
{
const steps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation'];
const currentIndex = steps.indexOf(state.currentStep);
if (currentIndex < steps.length - 1) {
return { ...state, currentStep: steps[currentIndex + 1] };
}
return state;
}
return state;
case 'PREV_STEP':
const prevSteps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation'];
const prevIndex = prevSteps.indexOf(state.currentStep);
if (prevIndex > 0) {
return { ...state, currentStep: prevSteps[prevIndex - 1] };
{
const prevSteps: CheckoutStepId[] = ['package', 'auth', 'payment', 'confirmation'];
const prevIndex = prevSteps.indexOf(state.currentStep);
if (prevIndex > 0) {
return { ...state, currentStep: prevSteps[prevIndex - 1] };
}
return state;
}
return state;
case 'GO_TO_STEP':
return { ...state, currentStep: action.payload };
case 'UPDATE_PAYMENT_INTENT':
@@ -105,7 +109,7 @@ interface CheckoutWizardProviderProps {
initialPackage?: CheckoutPackage;
packageOptions?: CheckoutPackage[];
initialStep?: CheckoutStepId;
initialAuthUser?: any;
initialAuthUser?: unknown;
initialIsAuthenticated?: boolean;
paddle?: {
environment?: string | null;
@@ -173,7 +177,7 @@ export function CheckoutWizardProvider({
dispatch({ type: 'SELECT_PACKAGE', payload: pkg });
}, []);
const setAuthUser = useCallback((user: any) => {
const setAuthUser = useCallback((user: unknown) => {
dispatch({ type: 'SET_AUTH_USER', payload: user });
}, []);

View File

@@ -39,7 +39,7 @@ export const AuthStep: React.FC<AuthStepProps> = ({ privacyHtml, googleProfile,
const page = usePage<{ locale?: string }>();
const locale = page.props.locale ?? "de";
const googleAuth = useMemo<GoogleAuthFlash>(() => {
const props = page.props as Record<string, any>;
const props = page.props as { googleAuth?: GoogleAuthFlash };
return props.googleAuth ?? {};
}, [page.props]);
const { isAuthenticated, authUser, setAuthUser, nextStep, selectedPackage } = useCheckoutWizard();

View File

@@ -8,10 +8,9 @@ import { cn } from "@/lib/utils";
interface ConfirmationStepProps {
onViewProfile?: () => void;
onGoToAdmin?: () => void;
}
export const ConfirmationStep: React.FC<ConfirmationStepProps> = ({ onViewProfile, onGoToAdmin }) => {
export const ConfirmationStep: React.FC<ConfirmationStepProps> = ({ onViewProfile }) => {
const { t } = useTranslation('marketing');
const { selectedPackage } = useCheckoutWizard();
const handleProfile = React.useCallback(() => {
@@ -22,14 +21,6 @@ export const ConfirmationStep: React.FC<ConfirmationStepProps> = ({ onViewProfil
window.location.href = '/settings/profile';
}, [onViewProfile]);
const handleAdmin = React.useCallback(() => {
if (typeof onGoToAdmin === 'function') {
onGoToAdmin();
return;
}
window.location.href = '/event-admin';
}, [onGoToAdmin]);
const packageName = selectedPackage?.name ?? '';
const onboardingItems = [

View File

@@ -149,17 +149,10 @@ export const PackageStep: React.FC = () => {
const { t } = useTranslation('marketing');
const { selectedPackage, packageOptions, setSelectedPackage, resetPaymentState } = useCheckoutWizard();
// Early return if no package is selected
if (!selectedPackage) {
return (
<div className="text-center py-8">
<p className="text-muted-foreground">{t('checkout.package_step.no_package_selected')}</p>
</div>
);
}
const comparablePackages = useMemo(() => {
if (!selectedPackage) {
return [];
}
// Filter by type and sort: free packages first, then by price ascending
return packageOptions
.filter((pkg: CheckoutPackage) => pkg.type === selectedPackage.type)
@@ -180,6 +173,14 @@ export const PackageStep: React.FC = () => {
resetPaymentState();
};
if (!selectedPackage) {
return (
<div className="text-center py-8">
<p className="text-muted-foreground">{t('checkout.package_step.no_package_selected')}</p>
</div>
);
}
return (
<div className="grid gap-8 lg:grid-cols-[2fr_1fr]">
<div className="space-y-6">

View File

@@ -139,7 +139,7 @@ export const PaymentStep: React.FC = () => {
const [couponLoading, setCouponLoading] = useState(false);
const paddleRef = useRef<typeof window.Paddle | null>(null);
const checkoutContainerRef = useRef<HTMLDivElement | null>(null);
const eventCallbackRef = useRef<(event: any) => void>();
const eventCallbackRef = useRef<(event: Record<string, unknown>) => void>();
const hasAutoAppliedCoupon = useRef(false);
const checkoutContainerClass = 'paddle-checkout-container';
@@ -149,7 +149,6 @@ export const PaymentStep: React.FC = () => {
}, [i18n.language]);
const isFree = useMemo(() => (selectedPackage ? Number(selectedPackage.price) <= 0 : false), [selectedPackage]);
const hasCoupon = Boolean(couponPreview);
const applyCoupon = useCallback(async (code: string) => {
if (!selectedPackage) {
@@ -339,7 +338,7 @@ export const PaymentStep: React.FC = () => {
console.info('[Checkout] Hosted checkout response', { status: response.status, rawBody });
}
let data: any = null;
let data: { checkout_url?: string; message?: string } | null = null;
try {
data = rawBody && rawBody.trim().startsWith('{') ? JSON.parse(rawBody) : null;
} catch (parseError) {
@@ -354,7 +353,7 @@ export const PaymentStep: React.FC = () => {
if (/^https?:\/\//i.test(trimmed)) {
checkoutUrl = trimmed;
} else if (trimmed.startsWith('<')) {
const match = trimmed.match(/https?:\/\/["'a-zA-Z0-9._~:\/?#\[\]@!$&'()*+,;=%-]+/);
const match = trimmed.match(/https?:\/\/["'a-zA-Z0-9._~:/?#@!$&'()*+,;=%-]+/);
if (match) {
checkoutUrl = match[0];
}
@@ -444,7 +443,7 @@ export const PaymentStep: React.FC = () => {
locale: paddleLocale,
},
},
eventCallback: (event: any) => eventCallbackRef.current?.(event),
eventCallback: (event: Record<string, unknown>) => eventCallbackRef.current?.(event),
});
inlineReady = true;

View File

@@ -43,6 +43,23 @@ export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail:
>
{({ processing, recentlySuccessful, errors }) => (
<>
<div className="grid gap-2">
<Label htmlFor="name">{t('auth.settings.profile.name', 'Name')}</Label>
<Input
id="name"
type="text"
className="mt-1 block w-full"
defaultValue={auth.user.name ?? ''}
name="name"
required
autoComplete="name"
placeholder={t('auth.settings.profile.name_placeholder', 'Dein Name')}
/>
<InputError className="mt-2" message={errors.name} />
</div>
<div className="grid gap-2">
<Label htmlFor="email">{t('auth.settings.profile.email', 'Email address')}</Label>
@@ -66,7 +83,7 @@ export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail:
<Input
id="username"
className="mt-1 block w-full"
defaultValue={(auth.user as any).username ?? ''}
defaultValue={auth.user?.username ?? ''}
name="username"
autoComplete="username"
placeholder={t('auth.settings.profile.username_placeholder')}
@@ -81,7 +98,7 @@ export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail:
<select
id="preferred_locale"
name="preferred_locale"
defaultValue={(auth.user as any).preferred_locale ?? 'en'}
defaultValue={auth.user?.preferred_locale ?? 'en'}
className="mt-1 block w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
{(supportedLocales ?? ['de', 'en']).map((l) => (