Fix some issues
This commit is contained in:
@@ -79,6 +79,9 @@ class ApiClient {
|
||||
const publicEndpoints = [
|
||||
'/demo/accounts',
|
||||
'/demo/session/create',
|
||||
'/public/contact',
|
||||
'/public/feedback',
|
||||
'/public/prelaunch-subscribe',
|
||||
];
|
||||
|
||||
// Endpoints that require authentication but not a tenant ID (user-level endpoints)
|
||||
|
||||
83
frontend/src/api/services/publicContact.ts
Normal file
83
frontend/src/api/services/publicContact.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Public Contact API Service
|
||||
* Handles public form submissions (contact, feedback, prelaunch)
|
||||
* These endpoints don't require authentication
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { getApiUrl } from '../../config/runtime';
|
||||
|
||||
const publicApiClient = axios.create({
|
||||
baseURL: getApiUrl(),
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Types
|
||||
export interface ContactFormData {
|
||||
name: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
bakery_name?: string;
|
||||
type: 'general' | 'technical' | 'sales' | 'feedback';
|
||||
subject: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface FeedbackFormData {
|
||||
name: string;
|
||||
email: string;
|
||||
category: 'suggestion' | 'bug' | 'feature' | 'praise' | 'complaint';
|
||||
title: string;
|
||||
description: string;
|
||||
rating?: number;
|
||||
}
|
||||
|
||||
export interface PrelaunchEmailData {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ContactFormResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// API Functions
|
||||
export const publicContactService = {
|
||||
/**
|
||||
* Submit a contact form
|
||||
*/
|
||||
submitContactForm: async (data: ContactFormData): Promise<ContactFormResponse> => {
|
||||
const response = await publicApiClient.post<ContactFormResponse>(
|
||||
'/v1/public/contact',
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit a feedback form
|
||||
*/
|
||||
submitFeedbackForm: async (data: FeedbackFormData): Promise<ContactFormResponse> => {
|
||||
const response = await publicApiClient.post<ContactFormResponse>(
|
||||
'/v1/public/feedback',
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit a prelaunch email subscription
|
||||
*/
|
||||
submitPrelaunchEmail: async (data: PrelaunchEmailData): Promise<ContactFormResponse> => {
|
||||
const response = await publicApiClient.post<ContactFormResponse>(
|
||||
'/v1/public/prelaunch-subscribe',
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
||||
export default publicContactService;
|
||||
185
frontend/src/components/domain/auth/PrelaunchEmailForm.tsx
Normal file
185
frontend/src/components/domain/auth/PrelaunchEmailForm.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Mail, Rocket, CheckCircle, Loader, ArrowLeft } from 'lucide-react';
|
||||
import { Button, Input, Card } from '../../ui';
|
||||
import { publicContactService } from '../../../api/services/publicContact';
|
||||
|
||||
interface PrelaunchEmailFormProps {
|
||||
onLoginClick?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PrelaunchEmailForm: React.FC<PrelaunchEmailFormProps> = ({
|
||||
onLoginClick,
|
||||
className = '',
|
||||
}) => {
|
||||
const { t } = useTranslation(['auth', 'common']);
|
||||
const [email, setEmail] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const validateEmail = (email: string): boolean => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
if (!email.trim()) {
|
||||
setError(t('auth:prelaunch.email_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateEmail(email)) {
|
||||
setError(t('auth:prelaunch.email_invalid'));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
await publicContactService.submitPrelaunchEmail({ email });
|
||||
setIsSubmitted(true);
|
||||
} catch {
|
||||
setError(t('auth:prelaunch.submit_error'));
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isSubmitted) {
|
||||
return (
|
||||
<Card className={`max-w-lg mx-auto p-8 ${className}`}>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto w-16 h-16 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mb-6">
|
||||
<CheckCircle className="w-8 h-8 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-3">
|
||||
{t('auth:prelaunch.success_title')}
|
||||
</h2>
|
||||
|
||||
<p className="text-[var(--text-secondary)] mb-6">
|
||||
{t('auth:prelaunch.success_message')}
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Button
|
||||
onClick={() => window.location.href = '/'}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
{t('auth:prelaunch.back_to_home')}
|
||||
</Button>
|
||||
|
||||
{onLoginClick && (
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
{t('auth:register.have_account')}{' '}
|
||||
<button
|
||||
onClick={onLoginClick}
|
||||
className="text-[var(--color-primary)] hover:underline font-medium"
|
||||
>
|
||||
{t('auth:register.login_link')}
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={`max-w-lg mx-auto p-8 ${className}`}>
|
||||
<div className="text-center mb-8">
|
||||
<div className="mx-auto w-16 h-16 bg-gradient-to-br from-amber-500 to-orange-500 rounded-full flex items-center justify-center mb-6 shadow-lg">
|
||||
<Rocket className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold text-[var(--text-primary)] mb-3">
|
||||
{t('auth:prelaunch.title')}
|
||||
</h1>
|
||||
|
||||
<p className="text-lg text-[var(--text-secondary)] mb-2">
|
||||
{t('auth:prelaunch.subtitle')}
|
||||
</p>
|
||||
|
||||
<p className="text-[var(--text-tertiary)]">
|
||||
{t('auth:prelaunch.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<Input
|
||||
type="email"
|
||||
label={t('auth:register.email')}
|
||||
placeholder={t('auth:register.email_placeholder')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
leftIcon={<Mail className="w-5 h-5" />}
|
||||
error={error || undefined}
|
||||
isRequired
|
||||
size="lg"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
className="w-full py-4 text-lg font-semibold"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader className="w-5 h-5 mr-2 animate-spin" />
|
||||
{t('auth:prelaunch.submitting')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Mail className="w-5 h-5 mr-2" />
|
||||
{t('auth:prelaunch.subscribe_button')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-8 pt-6 border-t border-[var(--border-primary)]">
|
||||
<h3 className="text-sm font-semibold text-[var(--text-primary)] mb-3">
|
||||
{t('auth:prelaunch.benefits_title')}
|
||||
</h3>
|
||||
<ul className="space-y-2 text-sm text-[var(--text-secondary)]">
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
{t('auth:prelaunch.benefit_1')}
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
{t('auth:prelaunch.benefit_2')}
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
{t('auth:prelaunch.benefit_3')}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{onLoginClick && (
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
{t('auth:register.have_account')}{' '}
|
||||
<button
|
||||
onClick={onLoginClick}
|
||||
className="text-[var(--color-primary)] hover:underline font-medium"
|
||||
>
|
||||
{t('auth:register.login_link')}
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrelaunchEmailForm;
|
||||
@@ -29,7 +29,12 @@ const getStripeKey = (): string => {
|
||||
return import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || 'pk_test_51234567890123456789012345678901234567890123456789012345678901234567890123456789012345';
|
||||
};
|
||||
|
||||
const stripePromise = loadStripe(getStripeKey());
|
||||
// Force Stripe to use test environment by loading from test endpoint
|
||||
const stripePromise = loadStripe(getStripeKey(), {
|
||||
stripeAccount: import.meta.env.VITE_STRIPE_ACCOUNT_ID,
|
||||
apiVersion: '2023-10-16',
|
||||
betas: ['elements_v2']
|
||||
});
|
||||
|
||||
interface RegistrationContainerProps {
|
||||
onSuccess?: () => void;
|
||||
|
||||
@@ -3,13 +3,15 @@ export { default as LoginForm } from './LoginForm';
|
||||
export { default as RegistrationContainer } from './RegistrationContainer';
|
||||
export { default as PasswordResetForm } from './PasswordResetForm';
|
||||
export { default as ProfileSettings } from './ProfileSettings';
|
||||
export { default as PrelaunchEmailForm } from './PrelaunchEmailForm';
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
export type {
|
||||
LoginFormProps,
|
||||
RegistrationContainerProps,
|
||||
RegistrationContainerProps,
|
||||
PasswordResetFormProps,
|
||||
ProfileSettingsProps
|
||||
ProfileSettingsProps,
|
||||
PrelaunchEmailFormProps
|
||||
} from './types';
|
||||
|
||||
// Component metadata for documentation
|
||||
|
||||
@@ -29,6 +29,11 @@ export interface ProfileSettingsProps {
|
||||
initialTab?: 'profile' | 'security' | 'preferences' | 'notifications';
|
||||
}
|
||||
|
||||
export interface PrelaunchEmailFormProps {
|
||||
onLoginClick?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Additional types for internal use
|
||||
export type RegistrationStep = 'personal' | 'bakery' | 'security' | 'verification';
|
||||
|
||||
|
||||
@@ -52,6 +52,8 @@ const getStripePublishableKey = (): string => {
|
||||
const stripePromise = loadStripe(getStripePublishableKey(), {
|
||||
betas: ['elements_v2'],
|
||||
locale: 'auto',
|
||||
stripeAccount: import.meta.env.VITE_STRIPE_ACCOUNT_ID,
|
||||
apiVersion: '2023-10-16'
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
SUBSCRIPTION_TIERS
|
||||
} from '../../api';
|
||||
import { getRegisterUrl } from '../../utils/navigation';
|
||||
import { PRELAUNCH_CONFIG } from '../../config/prelaunch';
|
||||
|
||||
type BillingCycle = 'monthly' | 'yearly';
|
||||
type DisplayMode = 'landing' | 'settings' | 'selection';
|
||||
@@ -411,12 +412,16 @@ export const SubscriptionPricingCards: React.FC<SubscriptionPricingCardsProps> =
|
||||
)
|
||||
: mode === 'settings'
|
||||
? t('ui.change_subscription', 'Cambiar Suscripción')
|
||||
: PRELAUNCH_CONFIG.enabled
|
||||
? t('ui.notify_me', 'Avísame del Lanzamiento')
|
||||
: t('ui.start_free_trial')}
|
||||
</Button>
|
||||
|
||||
{/* Footer */}
|
||||
<p className={`text-xs text-center mt-3 ${(isPopular || isSelected) && !isCurrentPlan ? 'text-white/80' : 'text-[var(--text-secondary)]'}`}>
|
||||
{showPilotBanner
|
||||
{PRELAUNCH_CONFIG.enabled
|
||||
? t('ui.prelaunch_footer', 'Lanzamiento oficial próximamente')
|
||||
: showPilotBanner
|
||||
? t('ui.free_trial_footer', { months: pilotTrialMonths })
|
||||
: t('ui.free_trial_footer', { months: 0 })
|
||||
}
|
||||
|
||||
17
frontend/src/config/prelaunch.ts
Normal file
17
frontend/src/config/prelaunch.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Pre-launch Mode Configuration
|
||||
* Uses build-time environment variables
|
||||
*
|
||||
* When VITE_PRELAUNCH_MODE=true:
|
||||
* - Registration page shows email capture form instead of Stripe flow
|
||||
* - Pricing cards link to the same page but show interest form
|
||||
*
|
||||
* When VITE_PRELAUNCH_MODE=false (or not set):
|
||||
* - Normal registration flow with Stripe payments
|
||||
*/
|
||||
|
||||
export const PRELAUNCH_CONFIG = {
|
||||
enabled: import.meta.env.VITE_PRELAUNCH_MODE === 'false',
|
||||
};
|
||||
|
||||
export default PRELAUNCH_CONFIG;
|
||||
@@ -116,6 +116,23 @@
|
||||
"secure_payment": "Your payment information is protected with end-to-end encryption",
|
||||
"payment_info_secure": "Your payment information is secure"
|
||||
},
|
||||
"prelaunch": {
|
||||
"title": "Coming Soon",
|
||||
"subtitle": "We're preparing something special for your bakery",
|
||||
"description": "Be the first to know when we officially launch. Leave your email and we'll notify you.",
|
||||
"email_required": "Email is required",
|
||||
"email_invalid": "Please enter a valid email address",
|
||||
"submit_error": "An error occurred. Please try again.",
|
||||
"subscribe_button": "Notify Me",
|
||||
"submitting": "Submitting...",
|
||||
"success_title": "You're on the list!",
|
||||
"success_message": "We'll send you an email when we're ready to launch. Thanks for your interest!",
|
||||
"back_to_home": "Back to Home",
|
||||
"benefits_title": "By subscribing you'll receive:",
|
||||
"benefit_1": "Early access to launch",
|
||||
"benefit_2": "Exclusive offers for early adopters",
|
||||
"benefit_3": "Product news and updates"
|
||||
},
|
||||
"steps": {
|
||||
"info": "Information",
|
||||
"subscription": "Plan",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"payment_details": "Payment Details",
|
||||
"payment_info_secure": "Your payment information is protected with end-to-end encryption",
|
||||
"updating_payment": "Updating...",
|
||||
"cancel": "Cancel"
|
||||
"cancel": "Cancel",
|
||||
"notify_me": "Notify Me of Launch",
|
||||
"prelaunch_footer": "Official launch coming soon"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,23 @@
|
||||
"go_to_login": "Ir a inicio de sesión",
|
||||
"try_again": "Intentar registro de nuevo"
|
||||
},
|
||||
"prelaunch": {
|
||||
"title": "Próximamente",
|
||||
"subtitle": "Estamos preparando algo especial para tu panadería",
|
||||
"description": "Sé el primero en saber cuándo lancemos oficialmente. Déjanos tu email y te avisaremos.",
|
||||
"email_required": "El correo electrónico es obligatorio",
|
||||
"email_invalid": "Por favor, introduce un correo electrónico válido",
|
||||
"submit_error": "Ha ocurrido un error. Por favor, inténtalo de nuevo.",
|
||||
"subscribe_button": "Quiero que me avisen",
|
||||
"submitting": "Enviando...",
|
||||
"success_title": "¡Genial! Te hemos apuntado",
|
||||
"success_message": "Te enviaremos un email cuando estemos listos para el lanzamiento. ¡Gracias por tu interés!",
|
||||
"back_to_home": "Volver al inicio",
|
||||
"benefits_title": "Al suscribirte recibirás:",
|
||||
"benefit_1": "Acceso anticipado al lanzamiento",
|
||||
"benefit_2": "Ofertas exclusivas para early adopters",
|
||||
"benefit_3": "Noticias y actualizaciones del producto"
|
||||
},
|
||||
"steps": {
|
||||
"info": "Información",
|
||||
"subscription": "Plan",
|
||||
|
||||
@@ -157,6 +157,8 @@
|
||||
"payment_details": "Detalles de Pago",
|
||||
"payment_info_secure": "Tu información de pago está protegida con encriptación de extremo a extremo",
|
||||
"updating_payment": "Actualizando...",
|
||||
"cancel": "Cancelar"
|
||||
"cancel": "Cancelar",
|
||||
"notify_me": "Avísame del Lanzamiento",
|
||||
"prelaunch_footer": "Lanzamiento oficial próximamente"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
AlertCircle,
|
||||
HelpCircle
|
||||
} from 'lucide-react';
|
||||
import { publicContactService } from '../../api/services/publicContact';
|
||||
|
||||
interface ContactMethod {
|
||||
id: string;
|
||||
@@ -73,25 +74,35 @@ const ContactPage: React.FC = () => {
|
||||
e.preventDefault();
|
||||
setSubmitStatus('loading');
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
// In production, this would be an actual API call
|
||||
console.log('Form submitted:', formState);
|
||||
|
||||
setSubmitStatus('success');
|
||||
setTimeout(() => {
|
||||
setSubmitStatus('idle');
|
||||
setFormState({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
bakeryName: '',
|
||||
subject: '',
|
||||
message: '',
|
||||
type: 'general',
|
||||
try {
|
||||
await publicContactService.submitContactForm({
|
||||
name: formState.name,
|
||||
email: formState.email,
|
||||
phone: formState.phone || undefined,
|
||||
bakery_name: formState.bakeryName || undefined,
|
||||
type: formState.type,
|
||||
subject: formState.subject,
|
||||
message: formState.message,
|
||||
});
|
||||
}, 3000);
|
||||
|
||||
setSubmitStatus('success');
|
||||
setTimeout(() => {
|
||||
setSubmitStatus('idle');
|
||||
setFormState({
|
||||
name: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
bakeryName: '',
|
||||
subject: '',
|
||||
message: '',
|
||||
type: 'general',
|
||||
});
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('Contact form submission error:', error);
|
||||
setSubmitStatus('error');
|
||||
setTimeout(() => setSubmitStatus('idle'), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
AlertCircle,
|
||||
Star
|
||||
} from 'lucide-react';
|
||||
import { publicContactService } from '../../api/services/publicContact';
|
||||
|
||||
interface FeedbackCategory {
|
||||
id: string;
|
||||
@@ -90,24 +91,33 @@ const FeedbackPage: React.FC = () => {
|
||||
e.preventDefault();
|
||||
setSubmitStatus('loading');
|
||||
|
||||
// Simulate API call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
// In production, this would be an actual API call
|
||||
console.log('Feedback submitted:', formState);
|
||||
|
||||
setSubmitStatus('success');
|
||||
setTimeout(() => {
|
||||
setSubmitStatus('idle');
|
||||
setFormState({
|
||||
name: '',
|
||||
email: '',
|
||||
category: 'suggestion',
|
||||
title: '',
|
||||
description: '',
|
||||
rating: 0,
|
||||
try {
|
||||
await publicContactService.submitFeedbackForm({
|
||||
name: formState.name,
|
||||
email: formState.email,
|
||||
category: formState.category,
|
||||
title: formState.title,
|
||||
description: formState.description,
|
||||
rating: formState.rating > 0 ? formState.rating : undefined,
|
||||
});
|
||||
}, 3000);
|
||||
|
||||
setSubmitStatus('success');
|
||||
setTimeout(() => {
|
||||
setSubmitStatus('idle');
|
||||
setFormState({
|
||||
name: '',
|
||||
email: '',
|
||||
category: 'suggestion',
|
||||
title: '',
|
||||
description: '',
|
||||
rating: 0,
|
||||
});
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
console.error('Feedback form submission error:', error);
|
||||
setSubmitStatus('error');
|
||||
setTimeout(() => setSubmitStatus('idle'), 5000);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryColor = (color: string) => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { RegistrationContainer } from '../../components/domain/auth';
|
||||
import { RegistrationContainer, PrelaunchEmailForm } from '../../components/domain/auth';
|
||||
import { PublicLayout } from '../../components/layout';
|
||||
import { PRELAUNCH_CONFIG } from '../../config/prelaunch';
|
||||
|
||||
const RegisterPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -14,6 +15,27 @@ const RegisterPage: React.FC = () => {
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
// Show prelaunch email form or full registration based on build-time config
|
||||
if (PRELAUNCH_CONFIG.enabled) {
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="centered"
|
||||
maxWidth="lg"
|
||||
headerProps={{
|
||||
showThemeToggle: true,
|
||||
showAuthButtons: false,
|
||||
showLanguageSelector: true,
|
||||
variant: "minimal"
|
||||
}}
|
||||
>
|
||||
<PrelaunchEmailForm
|
||||
onLoginClick={handleLoginClick}
|
||||
className="mx-auto"
|
||||
/>
|
||||
</PublicLayout>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PublicLayout
|
||||
variant="centered"
|
||||
|
||||
1
frontend/src/vite-env.d.ts
vendored
1
frontend/src/vite-env.d.ts
vendored
@@ -10,6 +10,7 @@ interface ImportMetaEnv {
|
||||
readonly VITE_PILOT_MODE_ENABLED?: string
|
||||
readonly VITE_PILOT_COUPON_CODE?: string
|
||||
readonly VITE_PILOT_TRIAL_MONTHS?: string
|
||||
readonly VITE_PRELAUNCH_MODE?: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
Reference in New Issue
Block a user