Enhance email templates with clickable links, branding, and improved styling
- Add clickable gallery links in all email templates - Include application logo in email header and footer (custom or PicPeak default) - Redesign emails with professional styling matching gallery login page - Gray background with white content box - PicPeak green header with centered logo - Clean typography and proper spacing - Responsive design for mobile devices - Styled call-to-action buttons - Footer with branding and copyright - Update email processor to fetch branding settings dynamically - Use proper API URLs for logo images in emails 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
EyeOff
|
||||
} from 'lucide-react';
|
||||
import { format, addDays } from 'date-fns';
|
||||
import { enUS, de } from 'date-fns/locale';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { Button, Input, Card } from '../../components/common';
|
||||
@@ -18,6 +19,7 @@ import { ThemeCustomizerEnhanced } from '../../components/admin/ThemeCustomizerE
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { eventsService } from '../../services/events.service';
|
||||
import { categoriesService } from '../../services/categories.service';
|
||||
import { settingsService } from '../../services/settings.service';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ThemeConfig, GALLERY_THEME_PRESETS } from '../../types/theme.types';
|
||||
|
||||
@@ -25,6 +27,7 @@ interface FormData {
|
||||
event_type: string;
|
||||
event_name: string;
|
||||
event_date: string;
|
||||
host_name: string;
|
||||
host_email: string;
|
||||
admin_email: string;
|
||||
password: string;
|
||||
@@ -53,7 +56,7 @@ const EVENT_TYPES = [
|
||||
|
||||
export const CreateEventPageEnhanced: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const isMountedRef = useRef(true);
|
||||
const [showThemeCustomizer, setShowThemeCustomizer] = useState(false);
|
||||
// const [showPreview, setShowPreview] = useState(false);
|
||||
@@ -68,6 +71,7 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
event_type: 'wedding',
|
||||
event_name: '',
|
||||
event_date: format(new Date(), 'yyyy-MM-dd'),
|
||||
host_name: '',
|
||||
host_email: '',
|
||||
admin_email: '',
|
||||
password: '',
|
||||
@@ -89,6 +93,22 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
queryFn: () => categoriesService.getGlobalCategories()
|
||||
});
|
||||
|
||||
// Fetch default settings
|
||||
const { data: settings } = useQuery({
|
||||
queryKey: ['admin-settings'],
|
||||
queryFn: () => settingsService.getAllSettings()
|
||||
});
|
||||
|
||||
// Update default expiration days when settings are loaded
|
||||
useEffect(() => {
|
||||
if (settings?.general_default_expiration_days) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
expires_in_days: settings.general_default_expiration_days
|
||||
}));
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
// Update theme when event type changes
|
||||
useEffect(() => {
|
||||
const recommendedPreset = EVENT_TYPE_PRESETS[formData.event_type];
|
||||
@@ -111,8 +131,21 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Create event error:', error);
|
||||
console.error('Error response:', error.response?.data);
|
||||
console.error('Error status:', error.response?.status);
|
||||
console.error('Full error object:', JSON.stringify(error.response, null, 2));
|
||||
const errorMessage = error.response?.data?.error || error.message || t('errors.eventCreationFailed');
|
||||
toast.error(errorMessage);
|
||||
|
||||
// If validation errors exist, show them
|
||||
if (error.response?.data?.errors) {
|
||||
const validationErrors = error.response.data.errors;
|
||||
console.error('Validation errors:', validationErrors);
|
||||
validationErrors.forEach((err: any) => {
|
||||
toast.error(`${err.param}: ${err.msg}`);
|
||||
});
|
||||
} else {
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -127,6 +160,10 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
newErrors.event_date = t('validation.eventDateRequired');
|
||||
}
|
||||
|
||||
if (!formData.host_name) {
|
||||
newErrors.host_name = t('validation.hostNameRequired');
|
||||
}
|
||||
|
||||
if (!formData.host_email) {
|
||||
newErrors.host_email = t('validation.hostEmailRequired');
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.host_email)) {
|
||||
@@ -164,10 +201,11 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
createMutation.mutate({
|
||||
const payload = {
|
||||
event_type: formData.event_type,
|
||||
event_name: formData.event_name,
|
||||
event_date: formData.event_date,
|
||||
host_name: formData.host_name,
|
||||
host_email: formData.host_email,
|
||||
admin_email: formData.admin_email,
|
||||
password: formData.password,
|
||||
@@ -176,7 +214,10 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
expiration_days: formData.expires_in_days,
|
||||
allow_user_uploads: formData.allow_user_uploads,
|
||||
upload_category_id: formData.upload_category_id,
|
||||
});
|
||||
};
|
||||
|
||||
console.log('Submitting payload:', payload);
|
||||
createMutation.mutate(payload);
|
||||
};
|
||||
|
||||
const handleInputChange = (field: keyof FormData) => (
|
||||
@@ -354,16 +395,27 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
{t('events.accessAndSecurity')}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Input
|
||||
type="email"
|
||||
label={t('events.hostEmail')}
|
||||
placeholder={t('events.hostEmailPlaceholder')}
|
||||
value={formData.host_email}
|
||||
onChange={handleInputChange('host_email')}
|
||||
error={errors.host_email}
|
||||
leftIcon={<Mail className="w-5 h-5" />}
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Input
|
||||
label={t('events.hostName')}
|
||||
placeholder={t('events.hostNamePlaceholder')}
|
||||
value={formData.host_name}
|
||||
onChange={handleInputChange('host_name')}
|
||||
error={errors.host_name}
|
||||
leftIcon={<Calendar className="w-5 h-5" />}
|
||||
/>
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
label={t('events.hostEmail')}
|
||||
placeholder={t('events.hostEmailPlaceholder')}
|
||||
value={formData.host_email}
|
||||
onChange={handleInputChange('host_email')}
|
||||
error={errors.host_email}
|
||||
leftIcon={<Mail className="w-5 h-5" />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
@@ -411,22 +463,23 @@ export const CreateEventPageEnhanced: React.FC = () => {
|
||||
<label className="block text-sm font-medium text-neutral-700 mb-2">
|
||||
{t('events.galleryExpiration')}
|
||||
</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Input
|
||||
type="number"
|
||||
value={formData.expires_in_days}
|
||||
onChange={handleInputChange('expires_in_days')}
|
||||
error={errors.expires_in_days}
|
||||
min={1}
|
||||
max={365}
|
||||
leftIcon={<Clock className="w-5 h-5" />}
|
||||
className="w-32"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-32">
|
||||
<Input
|
||||
type="number"
|
||||
value={formData.expires_in_days}
|
||||
onChange={handleInputChange('expires_in_days')}
|
||||
error={errors.expires_in_days}
|
||||
min={1}
|
||||
max={365}
|
||||
leftIcon={<Clock className="w-5 h-5" />}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-neutral-600">{t('events.daysAfterEvent')}</span>
|
||||
</div>
|
||||
{formData.event_date && (
|
||||
<p className="mt-2 text-sm text-neutral-500">
|
||||
{t('events.expiresOn')}: {format(addDays(new Date(formData.event_date), formData.expires_in_days), 'PPP')}
|
||||
{t('events.expiresOn')}: {format(addDays(new Date(formData.event_date), formData.expires_in_days), 'PPP', { locale: i18n.language === 'de' ? de : enUS })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user