gift voucher language fixes + move into user menu
This commit is contained in:
@@ -980,7 +980,24 @@
|
|||||||
"error_purchaser_email": "Bitte gib eine gültige E-Mail ein.",
|
"error_purchaser_email": "Bitte gib eine gültige E-Mail ein.",
|
||||||
"error_recipient_email": "Bitte gib eine gültige Empfänger-E-Mail ein.",
|
"error_recipient_email": "Bitte gib eine gültige Empfänger-E-Mail ein.",
|
||||||
"error_checkout": "Checkout konnte nicht gestartet werden. Bitte versuche es erneut.",
|
"error_checkout": "Checkout konnte nicht gestartet werden. Bitte versuche es erneut.",
|
||||||
"error": "Etwas ist schiefgelaufen. Bitte versuche es erneut."
|
"error": "Etwas ist schiefgelaufen. Bitte versuche es erneut.",
|
||||||
|
"lookup_title": "Gutscheinstatus prüfen",
|
||||||
|
"lookup_subtitle": "Du hast schon einen Code? Prüfe Wert, Gültigkeit und Status.",
|
||||||
|
"lookup_label": "Gutscheincode",
|
||||||
|
"lookup_cta": "Code prüfen",
|
||||||
|
"lookup_not_found": "Gutschein nicht gefunden oder nicht mehr gültig.",
|
||||||
|
"lookup_result_code": "Code: :code",
|
||||||
|
"lookup_result_value": "Wert: :amount :currency",
|
||||||
|
"lookup_result_expires": "Gültig bis :date",
|
||||||
|
"lookup_status": {
|
||||||
|
"issued": "Status: Ausgestellt (einlösbar)",
|
||||||
|
"redeemed": "Status: Eingelöst",
|
||||||
|
"refunded": "Status: Erstattet",
|
||||||
|
"expired": "Status: Abgelaufen",
|
||||||
|
"reminder": "Erinnerung geplant",
|
||||||
|
"expiry": "Ablauf-Hinweis geplant"
|
||||||
|
},
|
||||||
|
"too_many_attempts": "Zu viele Versuche. Bitte kurz warten und erneut probieren."
|
||||||
},
|
},
|
||||||
"not_found": {
|
"not_found": {
|
||||||
"title": "Seite nicht gefunden",
|
"title": "Seite nicht gefunden",
|
||||||
@@ -1002,4 +1019,4 @@
|
|||||||
"privacy": "Datenschutz",
|
"privacy": "Datenschutz",
|
||||||
"terms": "AGB"
|
"terms": "AGB"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -973,7 +973,24 @@
|
|||||||
"error_purchaser_email": "Please enter a valid email.",
|
"error_purchaser_email": "Please enter a valid email.",
|
||||||
"error_recipient_email": "Please enter a valid recipient email.",
|
"error_recipient_email": "Please enter a valid recipient email.",
|
||||||
"error_checkout": "Unable to start the checkout. Please try again.",
|
"error_checkout": "Unable to start the checkout. Please try again.",
|
||||||
"error": "Something went wrong. Please try again."
|
"error": "Something went wrong. Please try again.",
|
||||||
|
"lookup_title": "Check voucher status",
|
||||||
|
"lookup_subtitle": "Already have a code? See value, validity, and status.",
|
||||||
|
"lookup_label": "Voucher code",
|
||||||
|
"lookup_cta": "Check code",
|
||||||
|
"lookup_not_found": "Voucher not found or no longer valid.",
|
||||||
|
"lookup_result_code": "Code: :code",
|
||||||
|
"lookup_result_value": "Value: :amount :currency",
|
||||||
|
"lookup_result_expires": "Valid until :date",
|
||||||
|
"lookup_status": {
|
||||||
|
"issued": "Status: Issued (ready to redeem)",
|
||||||
|
"redeemed": "Status: Redeemed",
|
||||||
|
"refunded": "Status: Refunded",
|
||||||
|
"expired": "Status: Expired",
|
||||||
|
"reminder": "Reminder scheduled",
|
||||||
|
"expiry": "Expiry reminder scheduled"
|
||||||
|
},
|
||||||
|
"too_many_attempts": "Too many attempts. Please wait a moment and try again."
|
||||||
},
|
},
|
||||||
"not_found": {
|
"not_found": {
|
||||||
"title": "Page not found",
|
"title": "Page not found",
|
||||||
@@ -995,4 +1012,4 @@
|
|||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"terms": "Terms & Conditions"
|
"terms": "Terms & Conditions"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ i18n
|
|||||||
escapeValue: false,
|
escapeValue: false,
|
||||||
},
|
},
|
||||||
backend: {
|
backend: {
|
||||||
loadPath: '/lang/{{lng}}/{{ns}}.json',
|
// Cache-bust to ensure fresh translations when files change.
|
||||||
|
loadPath: '/lang/{{lng}}/{{ns}}.json?v=20250204',
|
||||||
},
|
},
|
||||||
react: {
|
react: {
|
||||||
useSuspense: false,
|
useSuspense: false,
|
||||||
|
|||||||
@@ -287,6 +287,16 @@ const MarketingLayout: React.FC<MarketingLayoutProps> = ({ children, title }) =>
|
|||||||
<DropdownMenuLabel className="font-sans-marketing text-xs uppercase tracking-wide text-gray-400 dark:text-gray-500">
|
<DropdownMenuLabel className="font-sans-marketing text-xs uppercase tracking-wide text-gray-400 dark:text-gray-500">
|
||||||
{user.name ?? user.email}
|
{user.name ?? user.email}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onSelect={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
router.visit(localizedPath('/voucher-status'));
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 font-sans-marketing dark:text-gray-100"
|
||||||
|
>
|
||||||
|
<LayoutDashboard className="h-4 w-4" />
|
||||||
|
<span>{t('gift.lookup_title', 'Gutscheinstatus')}</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={(event) => {
|
onSelect={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -10,34 +10,30 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import {
|
import {
|
||||||
fetchGiftVoucherTiers,
|
fetchGiftVoucherTiers,
|
||||||
createGiftVoucherCheckout,
|
createGiftVoucherCheckout,
|
||||||
fetchGiftVoucherByCode,
|
|
||||||
type GiftVoucherTier,
|
type GiftVoucherTier,
|
||||||
type GiftVoucherLookupResponse,
|
|
||||||
} from '@/lib/giftVouchers';
|
} from '@/lib/giftVouchers';
|
||||||
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useRateLimitHelper } from '@/hooks/useRateLimitHelper';
|
|
||||||
|
|
||||||
function useGiftVoucherTiers(initial: GiftVoucherTier[] = []) {
|
function useGiftVoucherTiers(initial: GiftVoucherTier[] = []) {
|
||||||
const [tiers, setTiers] = React.useState<GiftVoucherTier[]>(initial);
|
const [tiers, setTiers] = React.useState<GiftVoucherTier[]>(initial);
|
||||||
const [loading, setLoading] = React.useState(initial.length === 0);
|
const [loading, setLoading] = React.useState(initial.length === 0);
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
const { locale } = useLocalizedRoutes();
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (initial.length > 0) {
|
if (initial.length > 0) {
|
||||||
|
setTiers(initial.filter((tier) => tier.currency === 'EUR'));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetchGiftVoucherTiers()
|
fetchGiftVoucherTiers()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
const preferredCurrency = locale === 'en' ? 'USD' : 'EUR';
|
const eurOnly = data.filter((tier) => tier.currency === 'EUR');
|
||||||
const preferred = data.filter((tier) => tier.currency === preferredCurrency && tier.can_checkout);
|
setTiers(eurOnly.length > 0 ? eurOnly : data);
|
||||||
setTiers(preferred.length > 0 ? preferred : data);
|
|
||||||
})
|
})
|
||||||
.catch((err) => setError(err?.message || 'Failed to load tiers'))
|
.catch((err) => setError(err?.message || 'Failed to load tiers'))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, [initial, locale]);
|
}, [initial]);
|
||||||
|
|
||||||
return { tiers, loading, error };
|
return { tiers, loading, error };
|
||||||
}
|
}
|
||||||
@@ -57,11 +53,6 @@ function GiftVoucherPage({ tiers: initialTiers = [] }: { tiers: GiftVoucherTier[
|
|||||||
accept_terms: false,
|
accept_terms: false,
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = React.useState<Record<string, string | null>>({});
|
const [errors, setErrors] = React.useState<Record<string, string | null>>({});
|
||||||
const [lookupCode, setLookupCode] = React.useState('');
|
|
||||||
const [lookupResult, setLookupResult] = React.useState<GiftVoucherLookupResponse | null>(null);
|
|
||||||
const [lookupError, setLookupError] = React.useState<string | null>(null);
|
|
||||||
const [lookupLoading, setLookupLoading] = React.useState(false);
|
|
||||||
const rateLimit = useRateLimitHelper('voucher');
|
|
||||||
|
|
||||||
const selectedTierKey = form.tier_key;
|
const selectedTierKey = form.tier_key;
|
||||||
|
|
||||||
@@ -129,31 +120,6 @@ function GiftVoucherPage({ tiers: initialTiers = [] }: { tiers: GiftVoucherTier[
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLookup = async () => {
|
|
||||||
if (rateLimit.isLimited(lookupCode)) {
|
|
||||||
setLookupError(t('gift.too_many_attempts'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLookupLoading(true);
|
|
||||||
setLookupError(null);
|
|
||||||
setLookupResult(null);
|
|
||||||
try {
|
|
||||||
const result = await fetchGiftVoucherByCode(lookupCode);
|
|
||||||
if (result) {
|
|
||||||
setLookupResult(result);
|
|
||||||
rateLimit.clear(lookupCode);
|
|
||||||
} else {
|
|
||||||
setLookupError(t('gift.lookup_not_found'));
|
|
||||||
rateLimit.bump(lookupCode);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
setLookupError(error?.message || t('gift.lookup_not_found'));
|
|
||||||
rateLimit.bump(lookupCode);
|
|
||||||
} finally {
|
|
||||||
setLookupLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MarketingLayout title={t('gift.title')}>
|
<MarketingLayout title={t('gift.title')}>
|
||||||
@@ -294,52 +260,6 @@ function GiftVoucherPage({ tiers: initialTiers = [] }: { tiers: GiftVoucherTier[
|
|||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="lg:col-span-3">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>{t('gift.lookup_title')}</CardTitle>
|
|
||||||
<CardDescription>{t('gift.lookup_subtitle')}</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="grid gap-3 md:grid-cols-[2fr,1fr,auto]">
|
|
||||||
<div className="space-y-1 md:col-span-2">
|
|
||||||
<Label htmlFor="lookup_code">{t('gift.lookup_label')}</Label>
|
|
||||||
<Input
|
|
||||||
id="lookup_code"
|
|
||||||
placeholder="GIFT-XXXXXX"
|
|
||||||
value={lookupCode}
|
|
||||||
onChange={(e) => setLookupCode(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-end">
|
|
||||||
<Button onClick={onLookup} disabled={lookupLoading || !lookupCode.trim()}>
|
|
||||||
{lookupLoading ? t('gift.processing') : t('gift.lookup_cta')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{lookupError && <p className="md:col-span-3 text-sm text-destructive">{lookupError}</p>}
|
|
||||||
{lookupResult && (
|
|
||||||
<div className="md:col-span-3 space-y-1 rounded-lg border bg-muted/40 p-4">
|
|
||||||
<p className="text-sm font-semibold">
|
|
||||||
{t('gift.lookup_result_code', { code: lookupResult.code })}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('gift.lookup_result_value', {
|
|
||||||
amount: lookupResult.amount.toFixed(2),
|
|
||||||
currency: lookupResult.currency,
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
{lookupResult.expires_at && (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t('gift.lookup_result_expires', {
|
|
||||||
date: new Date(lookupResult.expires_at).toLocaleDateString(locale || undefined),
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t(`gift.lookup_status.${lookupResult.status}`, lookupResult.status)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
103
resources/js/pages/marketing/GiftVoucherStatus.tsx
Normal file
103
resources/js/pages/marketing/GiftVoucherStatus.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MarketingLayout from '@/layouts/mainWebsite';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { fetchGiftVoucherByCode, type GiftVoucherLookupResponse } from '@/lib/giftVouchers';
|
||||||
|
import { useRateLimitHelper } from '@/hooks/useRateLimitHelper';
|
||||||
|
|
||||||
|
const GiftVoucherStatus: React.FC = () => {
|
||||||
|
const { t, i18n } = useTranslation('marketing');
|
||||||
|
const [code, setCode] = React.useState('');
|
||||||
|
const [result, setResult] = React.useState<GiftVoucherLookupResponse | null>(null);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
const rateLimit = useRateLimitHelper('voucher');
|
||||||
|
|
||||||
|
const onLookup = async () => {
|
||||||
|
if (rateLimit.isLimited(code)) {
|
||||||
|
setError(t('gift.too_many_attempts'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setResult(null);
|
||||||
|
try {
|
||||||
|
const data = await fetchGiftVoucherByCode(code);
|
||||||
|
if (data) {
|
||||||
|
setResult(data);
|
||||||
|
rateLimit.clear(code);
|
||||||
|
} else {
|
||||||
|
setError(t('gift.lookup_not_found'));
|
||||||
|
rateLimit.bump(code);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e?.message || t('gift.lookup_not_found'));
|
||||||
|
rateLimit.bump(code);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarketingLayout title={t('gift.lookup_title')}>
|
||||||
|
<section className="bg-gradient-to-b from-background via-muted/30 to-background py-16">
|
||||||
|
<div className="mx-auto flex max-w-3xl flex-col gap-8 px-4 sm:px-6">
|
||||||
|
<div className="space-y-3 text-center">
|
||||||
|
<h1 className="text-3xl font-bold text-foreground">{t('gift.lookup_title')}</h1>
|
||||||
|
<p className="text-muted-foreground">{t('gift.lookup_subtitle')}</p>
|
||||||
|
</div>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t('gift.lookup_label')}</CardTitle>
|
||||||
|
<CardDescription>{t('gift.lookup_subtitle')}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="code">{t('gift.lookup_label')}</Label>
|
||||||
|
<Input
|
||||||
|
id="code"
|
||||||
|
placeholder="GIFT-XXXXXX"
|
||||||
|
value={code}
|
||||||
|
onChange={(e) => setCode(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={onLookup} disabled={loading || !code.trim()}>
|
||||||
|
{loading ? t('gift.processing', 'Checking...') : t('gift.lookup_cta')}
|
||||||
|
</Button>
|
||||||
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||||
|
{result && (
|
||||||
|
<div className="space-y-2 rounded-md border bg-muted/30 p-4 text-sm">
|
||||||
|
<p className="font-semibold">{t('gift.lookup_result_code', { code: result.code })}</p>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{t('gift.lookup_result_value', {
|
||||||
|
amount: result.amount.toFixed(2),
|
||||||
|
currency: result.currency,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{result.expires_at && (
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{t('gift.lookup_result_expires', {
|
||||||
|
date: new Date(result.expires_at).toLocaleDateString(i18n.language),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
{t(`gift.lookup_status.${result.status}`, result.status)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</MarketingLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
GiftVoucherStatus.layout = (page: React.ReactNode) => page;
|
||||||
|
|
||||||
|
export default GiftVoucherStatus;
|
||||||
@@ -281,4 +281,23 @@ return [
|
|||||||
'legal_note' => 'Geschenkgutscheine: 14 Tage Widerrufsrecht bis zur Einlösung; siehe Widerrufsbelehrung.',
|
'legal_note' => 'Geschenkgutscheine: 14 Tage Widerrufsrecht bis zur Einlösung; siehe Widerrufsbelehrung.',
|
||||||
'legal_link' => 'Widerrufsbelehrung öffnen',
|
'legal_link' => 'Widerrufsbelehrung öffnen',
|
||||||
],
|
],
|
||||||
|
'gift' => [
|
||||||
|
'lookup_title' => 'Gutscheinstatus prüfen',
|
||||||
|
'lookup_subtitle' => 'Du hast schon einen Code? Prüfe Wert, Gültigkeit und Status.',
|
||||||
|
'lookup_label' => 'Gutscheincode',
|
||||||
|
'lookup_cta' => 'Code prüfen',
|
||||||
|
'lookup_not_found' => 'Gutschein nicht gefunden oder nicht mehr gültig.',
|
||||||
|
'lookup_result_code' => 'Code: :code',
|
||||||
|
'lookup_result_value' => 'Wert: :amount :currency',
|
||||||
|
'lookup_result_expires' => 'Gültig bis :date',
|
||||||
|
'lookup_status' => [
|
||||||
|
'issued' => 'Status: Ausgestellt (einlösbar)',
|
||||||
|
'redeemed' => 'Status: Eingelöst',
|
||||||
|
'refunded' => 'Status: Erstattet',
|
||||||
|
'expired' => 'Status: Abgelaufen',
|
||||||
|
'reminder' => 'Erinnerung geplant',
|
||||||
|
'expiry' => 'Ablauf-Hinweis geplant',
|
||||||
|
],
|
||||||
|
'too_many_attempts' => 'Zu viele Versuche. Bitte kurz warten und erneut probieren.',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -281,4 +281,23 @@ return [
|
|||||||
'legal_note' => 'Gift vouchers: 14-day withdrawal right until redemption; see withdrawal policy.',
|
'legal_note' => 'Gift vouchers: 14-day withdrawal right until redemption; see withdrawal policy.',
|
||||||
'legal_link' => 'Open withdrawal policy',
|
'legal_link' => 'Open withdrawal policy',
|
||||||
],
|
],
|
||||||
|
'gift' => [
|
||||||
|
'lookup_title' => 'Check voucher status',
|
||||||
|
'lookup_subtitle' => 'Already have a code? See value, validity, and status.',
|
||||||
|
'lookup_label' => 'Voucher code',
|
||||||
|
'lookup_cta' => 'Check code',
|
||||||
|
'lookup_not_found' => 'Voucher not found or no longer valid.',
|
||||||
|
'lookup_result_code' => 'Code: :code',
|
||||||
|
'lookup_result_value' => 'Value: :amount :currency',
|
||||||
|
'lookup_result_expires' => 'Valid until :date',
|
||||||
|
'lookup_status' => [
|
||||||
|
'issued' => 'Status: Issued (ready to redeem)',
|
||||||
|
'redeemed' => 'Status: Redeemed',
|
||||||
|
'refunded' => 'Status: Refunded',
|
||||||
|
'expired' => 'Status: Expired',
|
||||||
|
'reminder' => 'Reminder scheduled',
|
||||||
|
'expiry' => 'Expiry reminder scheduled',
|
||||||
|
],
|
||||||
|
'too_many_attempts' => 'Too many attempts. Please wait a moment and try again.',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ Route::prefix('{locale}')
|
|||||||
Route::get('/gift-vouchers/{voucher}/print', GiftVoucherPrintController::class)
|
Route::get('/gift-vouchers/{voucher}/print', GiftVoucherPrintController::class)
|
||||||
->middleware('signed')
|
->middleware('signed')
|
||||||
->name('marketing.gift-vouchers.print');
|
->name('marketing.gift-vouchers.print');
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
Route::get('/voucher-status', fn () => Inertia::render('marketing/GiftVoucherStatus'))
|
||||||
|
->name('marketing.gift-voucher.status');
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('/impressum', [LegalPageController::class, 'show'])
|
Route::get('/impressum', [LegalPageController::class, 'show'])
|
||||||
->defaults('slug', 'impressum')
|
->defaults('slug', 'impressum')
|
||||||
|
|||||||
Reference in New Issue
Block a user