From c87cfb2c10e390d0fa3c3e0921d7e1c8e0682a2b Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 3 Oct 2025 15:31:54 +0200 Subject: [PATCH] =?UTF-8?q?mehr=20=C3=BCbersetzungen,=20added=20pending=20?= =?UTF-8?q?purchase=20indicator.=20datenschutzfenster=20funktioniert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Auth/MarketingRegisterController.php | 28 ++++++++-- .../Controllers/StripeWebhookController.php | 23 ++++++++ app/Models/User.php | 2 + ...00_add_pending_purchase_to_users_table.php | 28 ++++++++++ public/lang/de/auth.json | 13 ++++- public/lang/de/common.json | 35 ++++++++++++ public/lang/de/marketing.json | 45 ++++++++++++++- public/lang/en/auth.json | 13 ++++- public/lang/en/common.json | 35 ++++++++++++ public/lang/en/marketing.json | 45 ++++++++++++++- resources/js/i18n.ts | 32 +++++++++++ .../js/layouts/marketing/MarketingLayout.tsx | 55 +++++++++++++++++-- resources/js/pages/auth/register.tsx | 20 +++---- resources/js/pages/marketing/Home.tsx | 16 +++--- resources/js/pages/marketing/Packages.tsx | 38 +++++++++---- .../views/legal/datenschutz-partial.blade.php | 20 +++++++ routes/web.php | 14 +++-- 17 files changed, 410 insertions(+), 52 deletions(-) create mode 100644 database/migrations/2025_10_03_000000_add_pending_purchase_to_users_table.php create mode 100644 public/lang/de/common.json create mode 100644 public/lang/en/common.json create mode 100644 resources/js/i18n.ts create mode 100644 resources/views/legal/datenschutz-partial.blade.php diff --git a/app/Http/Controllers/Auth/MarketingRegisterController.php b/app/Http/Controllers/Auth/MarketingRegisterController.php index f78bbef..caf5ed0 100644 --- a/app/Http/Controllers/Auth/MarketingRegisterController.php +++ b/app/Http/Controllers/Auth/MarketingRegisterController.php @@ -24,14 +24,16 @@ class MarketingRegisterController extends Controller /** * Show the registration page. */ - public function create(Request $request, $package_id = null): Response + public function create(Request $request): Response { + $package_id = $request->query('package_id'); $package = $package_id ? Package::find($package_id) : null; - //App::setLocale('de'); + $privacyHtml = view('legal.datenschutz-partial')->render(); return Inertia::render('auth/register', [ 'package' => $package, + 'privacyHtml' => $privacyHtml, ]); } @@ -65,8 +67,13 @@ class MarketingRegisterController extends Controller 'phone' => $validated['phone'], 'password' => Hash::make($validated['password']), 'role' => 'user', + 'pending_purchase' => !empty($validated['package_id']), ]); + if ($user->pending_purchase) { + session()->put('pending_user_id', $user->id); + } + if ($shouldAutoVerify) { $user->forceFill(['email_verified_at' => now()])->save(); } @@ -113,6 +120,7 @@ class MarketingRegisterController extends Controller $package = Package::find($validated['package_id']); if (!$package) { // No action if package not found + return redirect()->route('dashboard')->with('success', 'Registrierung erfolgreich! Bitte verifizieren Sie Ihre E-Mail.'); } else if ((float) $package->price <= 0.0) { // Assign free package TenantPackage::create([ @@ -133,20 +141,28 @@ class MarketingRegisterController extends Controller $tenant->update(['subscription_status' => 'active']); - $user->update(['role' => 'tenant_admin']); + $user->update(['role' => 'tenant_admin', 'pending_purchase' => false]); Auth::login($user); // Re-login to refresh session + + if ($shouldAutoVerify) { + return redirect()->route('dashboard')->with('success', 'Registrierung und Package-Zuweisung erfolgreich!'); + } else { + return redirect()->route('verification.notice')->with('status', 'registration-success'); + } } else { - return redirect()->route('buy.packages', $package->id); + // For paid package, keep pending_purchase true, redirect to buy + return redirect()->route('buy.packages', $package->id)->with('success', 'Registrierung erfolgreich! Fahren Sie mit dem Kauf fort.'); } } + // No package if ($shouldAutoVerify) { - return Inertia::location($dashboardUrl); + return redirect()->route('dashboard')->with('success', 'Registrierung erfolgreich!'); } session()->flash('status', 'registration-success'); - return Inertia::location(route('verification.notice')); + return redirect()->route('verification.notice'); } } diff --git a/app/Http/Controllers/StripeWebhookController.php b/app/Http/Controllers/StripeWebhookController.php index c7fc7a8..06bc30d 100644 --- a/app/Http/Controllers/StripeWebhookController.php +++ b/app/Http/Controllers/StripeWebhookController.php @@ -87,6 +87,17 @@ class StripeWebhookController extends Controller return; } + // Activate user if pending purchase + $user = \App\Models\User::where('id', $tenant->user_id ?? $userId)->first(); + if ($user && $user->pending_purchase) { + $user->update([ + 'email_verified_at' => now(), + 'role' => 'tenant_admin', + 'pending_purchase' => false, + ]); + Log::info('User activated after purchase: ' . $user->id); + } + // Create PackagePurchase for one-off payment \App\Models\PackagePurchase::create([ 'tenant_id' => $tenantId, @@ -209,6 +220,18 @@ class StripeWebhookController extends Controller return; } + // Activate user if pending purchase + $tenant = \App\Models\Tenant::find($tenantId); + $user = $tenant ? $tenant->user : null; + if ($user && $user->pending_purchase) { + $user->update([ + 'email_verified_at' => now(), + 'role' => 'tenant_admin', + 'pending_purchase' => false, + ]); + Log::info('User activated after subscription purchase: ' . $user->id); + } + // Activate TenantPackage for initial subscription \App\Models\TenantPackage::updateOrCreate( [ diff --git a/app/Models/User.php b/app/Models/User.php index faa0b96..60ea225 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -31,6 +31,7 @@ class User extends Authenticatable implements MustVerifyEmail 'address', 'phone', 'role', + 'pending_purchase', ]; /** @@ -53,6 +54,7 @@ class User extends Authenticatable implements MustVerifyEmail return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'pending_purchase' => 'boolean', ]; } diff --git a/database/migrations/2025_10_03_000000_add_pending_purchase_to_users_table.php b/database/migrations/2025_10_03_000000_add_pending_purchase_to_users_table.php new file mode 100644 index 0000000..1ce1323 --- /dev/null +++ b/database/migrations/2025_10_03_000000_add_pending_purchase_to_users_table.php @@ -0,0 +1,28 @@ +boolean('pending_purchase')->default(false)->after('role'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('pending_purchase'); + }); + } +}; \ No newline at end of file diff --git a/public/lang/de/auth.json b/public/lang/de/auth.json index 7b75399..8719f68 100644 --- a/public/lang/de/auth.json +++ b/public/lang/de/auth.json @@ -44,7 +44,18 @@ "has_account": "Bereits registriert?", "login": "Anmelden", "errors_title": "Fehler bei der Registrierung:", - "privacy_policy": "Datenschutzerklärung" + "privacy_policy": "Datenschutzerklärung", + "errors": { + "first_name": "Vorname ist erforderlich.", + "last_name": "Nachname ist erforderlich.", + "email": "E-Mail ist erforderlich.", + "address": "Adresse ist erforderlich.", + "phone": "Telefonnummer ist erforderlich.", + "username": "Benutzername ist erforderlich.", + "password": "Passwort ist erforderlich.", + "confirm_password": "Passwortbestätigung stimmt nicht überein.", + "privacy_consent": "Sie müssen der Datenschutzerklärung zustimmen." + } }, "header": { "login": "Anmelden", diff --git a/public/lang/de/common.json b/public/lang/de/common.json new file mode 100644 index 0000000..5dcca71 --- /dev/null +++ b/public/lang/de/common.json @@ -0,0 +1,35 @@ +{ + "required": "*", + "unlimited": "Unbegrenzt", + "loading": "Laden...", + "included": "Enthalten", + "star": "Stern", + "date": { + "format": "d.m.Y" + }, + "pagination": { + "previous": "Zurück", + "next": "Weiter" + }, + "contact": { + "errors": { + "name": "Name ist erforderlich.", + "email": "E-Mail ist erforderlich.", + "message": "Nachricht ist erforderlich." + } + }, + "currency": { + "euro": "€" + }, + "testimonials": { + "anna": { + "name": "Anna M." + }, + "max": { + "name": "Max S." + }, + "lisa": { + "name": "Lisa K." + } + } +} \ No newline at end of file diff --git a/public/lang/de/marketing.json b/public/lang/de/marketing.json index abeda9c..7914358 100644 --- a/public/lang/de/marketing.json +++ b/public/lang/de/marketing.json @@ -120,7 +120,12 @@ "euro": "€" }, "view_details": "Details ansehen", - "feature": "Feature" + "feature": "Feature", + "details": "Details", + "customer_opinions": "Kundenmeinungen", + "errors": { + "select_package": "Bitte wählen Sie ein Paket aus." + } }, "blog": { "title": "Fotospiel - Blog", @@ -137,7 +142,11 @@ "our_blog": "Unser Blog", "latest_posts": "Neueste Beiträge", "no_posts": "Keine Beiträge verfügbar.", - "read_more_link": "Mehr lesen" + "read_more_link": "Mehr lesen", + "date_format": "Veröffentlicht am :date", + "post": { + "alt": "Beitragsbild" + } }, "kontakt": { "title": "Kontakt - Fotospiel", @@ -262,5 +271,37 @@ "meta": { "title": "Fotospiel - Sammle Gastfotos für Events mit QR-Codes", "description": "Sammle Gastfotos für Events mit QR-Codes. Unsere sichere PWA-Plattform für Gäste und Organisatoren – einfach, mobil und datenschutzkonform." + }, + "common": { + "unlimited": "Unbegrenzt", + "required": "*", + "loading": "Laden...", + "included": "Enthalten", + "star": "Stern", + "date": { + "format": "d.m.Y" + }, + "pagination": { + "previous": "Zurück", + "next": "Weiter" + }, + "contact": { + "errors": { + "name": "Name ist erforderlich.", + "email": "E-Mail ist erforderlich.", + "message": "Nachricht ist erforderlich." + } + }, + "testimonials": { + "anna": { + "name": "Anna M." + }, + "max": { + "name": "Max S." + }, + "lisa": { + "name": "Lisa K." + } + } } } \ No newline at end of file diff --git a/public/lang/en/auth.json b/public/lang/en/auth.json index 18cf943..f4bf39a 100644 --- a/public/lang/en/auth.json +++ b/public/lang/en/auth.json @@ -44,7 +44,18 @@ "has_account": "Already registered?", "login": "Log in", "errors_title": "Registration errors:", - "privacy_policy": "Privacy policy" + "privacy_policy": "Privacy policy", + "errors": { + "first_name": "First name is required.", + "last_name": "Last name is required.", + "email": "Email is required.", + "address": "Address is required.", + "phone": "Phone number is required.", + "username": "Username is required.", + "password": "Password is required.", + "confirm_password": "Confirm password does not match.", + "privacy_consent": "You must agree to the privacy policy." + } }, "header": { "login": "Log in", diff --git a/public/lang/en/common.json b/public/lang/en/common.json new file mode 100644 index 0000000..9105a23 --- /dev/null +++ b/public/lang/en/common.json @@ -0,0 +1,35 @@ +{ + "required": "*", + "unlimited": "Unlimited", + "loading": "Loading...", + "included": "Included", + "star": "Star", + "date": { + "format": "M d, Y" + }, + "pagination": { + "previous": "Previous", + "next": "Next" + }, + "contact": { + "errors": { + "name": "Name is required.", + "email": "Email is required.", + "message": "Message is required." + } + }, + "currency": { + "euro": "€" + }, + "testimonials": { + "anna": { + "name": "Anna M." + }, + "max": { + "name": "Max S." + }, + "lisa": { + "name": "Lisa K." + } + } +} \ No newline at end of file diff --git a/public/lang/en/marketing.json b/public/lang/en/marketing.json index 9115cae..23dd142 100644 --- a/public/lang/en/marketing.json +++ b/public/lang/en/marketing.json @@ -111,7 +111,12 @@ }, "what_customers_say": "What our customers say", "close": "Close", - "to_order": "Order Now" + "to_order": "Order Now", + "details": "Details", + "customer_opinions": "Customer Opinions", + "errors": { + "select_package": "Please select a package." + } }, "blog": { "title": "Fotospiel - Blog", @@ -128,7 +133,11 @@ "our_blog": "Our Blog", "latest_posts": "Latest Posts", "no_posts": "No posts available.", - "read_more_link": "Read More" + "read_more_link": "Read More", + "date_format": "Published on :date", + "post": { + "alt": "Post Image" + } }, "kontakt": { "title": "Contact - Fotospiel", @@ -253,5 +262,37 @@ "meta": { "title": "Fotospiel - Collect Guest Photos for Events with QR Codes", "description": "Collect guest photos for events with QR codes. Our secure PWA platform for guests and organizers – simple, mobile, and privacy-compliant." + }, + "common": { + "unlimited": "Unlimited", + "required": "*", + "loading": "Loading...", + "included": "Included", + "star": "Star", + "date": { + "format": "M d, Y" + }, + "pagination": { + "previous": "Previous", + "next": "Next" + }, + "contact": { + "errors": { + "name": "Name is required.", + "email": "Email is required.", + "message": "Message is required." + } + }, + "testimonials": { + "anna": { + "name": "Anna M." + }, + "max": { + "name": "Max S." + }, + "lisa": { + "name": "Lisa K." + } + } } } \ No newline at end of file diff --git a/resources/js/i18n.ts b/resources/js/i18n.ts new file mode 100644 index 0000000..26c7da8 --- /dev/null +++ b/resources/js/i18n.ts @@ -0,0 +1,32 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import Backend from 'i18next-http-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + fallbackLng: 'de', + supportedLngs: ['de', 'en'], + ns: ['marketing', 'auth', 'common'], + defaultNS: 'marketing', + debug: import.meta.env.DEV, + interpolation: { + escapeValue: false, + }, + backend: { + loadPath: '/lang/{{lng}}/{{ns}}.json', + }, + detection: { + order: ['path', 'localStorage', 'htmlTag'], + lookupFromPathIndex: 0, + caches: ['localStorage'], + }, + react: { + useSuspense: false, + }, + }); + +export default i18n; \ No newline at end of file diff --git a/resources/js/layouts/marketing/MarketingLayout.tsx b/resources/js/layouts/marketing/MarketingLayout.tsx index 4a185ee..4696ff3 100644 --- a/resources/js/layouts/marketing/MarketingLayout.tsx +++ b/resources/js/layouts/marketing/MarketingLayout.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { Head, Link, usePage } from '@inertiajs/react'; +import React, { useEffect } from 'react'; +import { Head, Link, usePage, router } from '@inertiajs/react'; import { useTranslation } from 'react-i18next'; interface MarketingLayoutProps { @@ -10,6 +10,14 @@ interface MarketingLayoutProps { const MarketingLayout: React.FC = ({ children, title }) => { const { t } = useTranslation('marketing'); const { url } = usePage(); + const i18n = useTranslation(); + + useEffect(() => { + const locale = url.startsWith('/en/') ? 'en' : 'de'; + if (i18n.i18n.language !== locale) { + i18n.i18n.changeLanguage(locale); + } + }, [url, i18n]); const { translations } = usePage().props as any; const marketing = translations?.marketing || {}; @@ -39,6 +47,43 @@ const MarketingLayout: React.FC = ({ children, title }) =>
+
+
+ +
+
{children}
@@ -47,13 +92,13 @@ const MarketingLayout: React.FC = ({ children, title }) =>

© 2025 Fotospiel. Alle Rechte vorbehalten.

- {t('nav.privacy', getString('nav.privacy', 'Datenschutz'))} + {t('nav.privacy')} - {t('nav.impressum', getString('nav.impressum', 'Impressum'))} + {t('nav.impressum')} - {t('nav.contact', getString('nav.contact', 'Kontakt'))} + {t('nav.contact')}
diff --git a/resources/js/pages/auth/register.tsx b/resources/js/pages/auth/register.tsx index 3907e24..d5e1fe8 100644 --- a/resources/js/pages/auth/register.tsx +++ b/resources/js/pages/auth/register.tsx @@ -19,7 +19,7 @@ import MarketingLayout from '@/layouts/marketing/MarketingLayout'; export default function Register({ package: initialPackage, privacyHtml }: RegisterProps) { const [privacyOpen, setPrivacyOpen] = useState(false); const [hasTriedSubmit, setHasTriedSubmit] = useState(false); - const { t } = useTranslation('auth'); + const { t } = useTranslation(['auth', 'common']); const { data, setData, post, processing, errors, clearErrors } = useForm({ username: '', @@ -87,7 +87,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -112,7 +112,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -137,7 +137,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -162,7 +162,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -187,7 +187,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -212,7 +212,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -237,7 +237,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -265,7 +265,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
@@ -326,7 +326,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
    {Object.entries(errors).map(([key, value]) => (
  • - {key.replace('_', ' ')}: {value} + {t(`register.errors.${key}`)}: {value}
  • ))}
diff --git a/resources/js/pages/marketing/Home.tsx b/resources/js/pages/marketing/Home.tsx index fa15e18..9cb8d07 100644 --- a/resources/js/pages/marketing/Home.tsx +++ b/resources/js/pages/marketing/Home.tsx @@ -122,8 +122,8 @@ const Home: React.FC = ({ packages }) => {

{pkg.name}

{pkg.description}

-

{pkg.price} {t('home.currency.euro')}

- +

{pkg.price} {t('common.currency.euro')}

+ {t('home.view_details')}
@@ -144,7 +144,7 @@ const Home: React.FC = ({ packages }) => {
= ({ packages }) => {
= ({ packages }) => {