Added opaque join-token support across backend and frontend: new migration/model/service/endpoints, guest controllers now resolve tokens, and the demo seeder seeds a token. Tenant event details list/manage tokens with copy/revoke actions, and the guest PWA uses tokens end-to-end (routing, storage, uploads, achievements, etc.). Docs TODO updated to reflect completed steps.

This commit is contained in:
Codex Agent
2025-10-12 10:32:37 +02:00
parent d04e234ca0
commit 9394c3171e
73 changed files with 3277 additions and 911 deletions

View File

@@ -16,7 +16,8 @@ interface Purchase {
}
const ProfileOrders = () => {
const { purchases } = usePage().props as any;
const page = usePage<{ purchases?: Purchase[] }>();
const purchases = page.props.purchases ?? [];
return (
<div className="container mx-auto py-8">
@@ -58,4 +59,4 @@ const ProfileOrders = () => {
);
};
export default ProfileOrders;
export default ProfileOrders;

View File

@@ -20,6 +20,19 @@ interface RegisterFormProps {
locale?: string;
}
type RegisterFormFields = {
username: string;
email: string;
password: string;
password_confirmation: string;
first_name: string;
last_name: string;
address: string;
phone: string;
privacy_consent: boolean;
package_id: number | null;
};
export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale }: RegisterFormProps) {
const [privacyOpen, setPrivacyOpen] = useState(false);
const [hasTriedSubmit, setHasTriedSubmit] = useState(false);
@@ -28,7 +41,7 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
const page = usePage<{ errors: Record<string, string>; locale?: string; auth?: { user?: any | null } }>();
const resolvedLocale = locale ?? page.props.locale ?? 'de';
const { data, setData, errors, clearErrors, reset, setError } = useForm({
const { data, setData, errors, clearErrors, reset, setError } = useForm<RegisterFormFields>({
username: '',
email: '',
password: '',
@@ -91,10 +104,12 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale
const json = await response.json();
const fieldErrors = json?.errors ?? {};
Object.entries(fieldErrors).forEach(([key, message]) => {
if (Array.isArray(message) && message.length > 0) {
setError(key, message[0] as string);
} else if (typeof message === 'string') {
setError(key, message);
if (key in data) {
const fieldKey = key as keyof RegisterFormFields;
const firstMessage = Array.isArray(message) ? (message[0] as string | undefined) : typeof message === 'string' ? message : undefined;
if (firstMessage) {
setError(fieldKey, firstMessage);
}
}
});
toast.error(t('register.validation_failed', 'Bitte pruefen Sie Ihre Eingaben.'));
@@ -409,4 +424,3 @@ export default function RegisterForm({ packageId, onSuccess, privacyHtml, locale

View File

@@ -3,6 +3,7 @@ import { store } from '@/actions/App/Http/Controllers/Auth/PasswordResetLinkCont
import { login } from '@/routes';
import { Form, Head } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import InputError from '@/components/input-error';
import TextLink from '@/components/text-link';

View File

@@ -10,6 +10,7 @@ interface CheckoutWizardPageProps {
package: CheckoutPackage;
packageOptions: CheckoutPackage[];
stripePublishableKey: string;
paypalClientId: string;
privacyHtml: string;
}
@@ -17,6 +18,7 @@ export default function CheckoutWizardPage({
package: initialPackage,
packageOptions,
stripePublishableKey,
paypalClientId,
privacyHtml,
}: CheckoutWizardPageProps) {
const page = usePage<{ auth?: { user?: { id: number; email: string; name?: string; pending_purchase?: boolean } | null } }>();
@@ -57,6 +59,7 @@ export default function CheckoutWizardPage({
initialPackage={initialPackage}
packageOptions={dedupedOptions}
stripePublishableKey={stripePublishableKey}
paypalClientId={paypalClientId}
privacyHtml={privacyHtml}
initialAuthUser={currentUser ? { id: currentUser.id, email: currentUser.email ?? '', name: currentUser.name ?? undefined, pending_purchase: Boolean(currentUser.pending_purchase) } : null}
/>

View File

@@ -14,6 +14,7 @@ interface CheckoutWizardProps {
initialPackage: CheckoutPackage;
packageOptions: CheckoutPackage[];
stripePublishableKey: string;
paypalClientId: string;
privacyHtml: string;
initialAuthUser?: {
id: number;
@@ -50,7 +51,7 @@ const baseStepConfig: { id: CheckoutStepId; titleKey: string; descriptionKey: st
detailsKey: 'checkout.confirmation_step.description'
},
];
const WizardBody: React.FC<{ stripePublishableKey: string; privacyHtml: string }> = ({ stripePublishableKey, privacyHtml }) => {
const WizardBody: React.FC<{ stripePublishableKey: string; paypalClientId: string; privacyHtml: string }> = ({ stripePublishableKey, paypalClientId, privacyHtml }) => {
const { t } = useTranslation('marketing');
const { currentStep, nextStep, previousStep } = useCheckoutWizard();
@@ -82,7 +83,9 @@ const WizardBody: React.FC<{ stripePublishableKey: string; privacyHtml: string }
<div className="space-y-6">
{currentStep === "package" && <PackageStep />}
{currentStep === "auth" && <AuthStep privacyHtml={privacyHtml} />}
{currentStep === "payment" && <PaymentStep stripePublishableKey={stripePublishableKey} />}
{currentStep === "payment" && (
<PaymentStep stripePublishableKey={stripePublishableKey} paypalClientId={paypalClientId} />
)}
{currentStep === "confirmation" && <ConfirmationStep />}
</div>
@@ -102,6 +105,7 @@ export const CheckoutWizard: React.FC<CheckoutWizardProps> = ({
initialPackage,
packageOptions,
stripePublishableKey,
paypalClientId,
privacyHtml,
initialAuthUser,
initialStep,
@@ -115,7 +119,7 @@ export const CheckoutWizard: React.FC<CheckoutWizardProps> = ({
initialAuthUser={initialAuthUser ?? undefined}
initialIsAuthenticated={Boolean(initialAuthUser)}
>
<WizardBody stripePublishableKey={stripePublishableKey} privacyHtml={privacyHtml} />
<WizardBody stripePublishableKey={stripePublishableKey} paypalClientId={paypalClientId} privacyHtml={privacyHtml} />
</CheckoutWizardProvider>
);
};

View File

@@ -1,4 +1,4 @@
import { store } from '@/actions/App/Http/Controllers/Settings/PasswordController';
import PasswordController from '@/actions/App/Http/Controllers/Settings/PasswordController';
import InputError from '@/components/input-error';
import AppLayout from '@/layouts/app-layout';
import SettingsLayout from '@/layouts/settings/layout';
@@ -35,7 +35,7 @@ export default function Password() {
<HeadingSmall title={t('auth.settings.password.section_title', 'Update password')} description={t('auth.settings.password.description', 'Ensure your account is using a long, random password to stay secure')} />
<Form
{...store.form()}
{...PasswordController.update.form()}
options={{
preserveScroll: true,
}}

View File

@@ -1,4 +1,4 @@
import { store } from '@/actions/App/Http/Controllers/Settings/ProfileController';
import ProfileController from '@/actions/App/Http/Controllers/Settings/ProfileController';
import { send } from '@/routes/verification';
import { type BreadcrumbItem, type SharedData } from '@/types';
import { Transition } from '@headlessui/react';