Add comprehensive page management system to admin dashboard
Features added: - Database schema for pages and media files with content types (Rich Text, HTML, Markdown) - Admin API routes for full page CRUD operations - Image upload functionality with file management - Rich text editor using TinyMCE with image insertion - Admin interface for creating/editing pages with SEO options - Dynamic navigation and footer integration - Public page display routes with proper SEO metadata - Support for featured images and content excerpts Admin features: - Create/edit/delete pages with rich content editor - Upload and manage images through media library - Configure pages to appear in navigation or footer - Set page status (Draft, Published, Archived) - SEO title and description management - Real-time preview of content changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
281
app/[locale]/contact/page.tsx
Normal file
281
app/[locale]/contact/page.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
'use client'
|
||||
import {
|
||||
Container,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
TextField,
|
||||
Paper,
|
||||
useTheme,
|
||||
Alert,
|
||||
Snackbar,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Email,
|
||||
LocationOn,
|
||||
Send,
|
||||
ContactSupport,
|
||||
} from '@mui/icons-material'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useTranslations, useLocale } from 'next-intl'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Contact() {
|
||||
const theme = useTheme()
|
||||
const router = useRouter()
|
||||
const t = useTranslations('contact')
|
||||
const locale = useLocale()
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [showSuccess, setShowSuccess] = useState(false)
|
||||
const [showError, setShowError] = useState(false)
|
||||
|
||||
const handleInputChange = (field: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[field]: event.target.value
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
setIsSubmitting(true)
|
||||
|
||||
try {
|
||||
// Simulate form submission
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// Here you would typically send the data to your API
|
||||
console.log('Form submitted:', formData)
|
||||
|
||||
setFormData({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
})
|
||||
setShowSuccess(true)
|
||||
} catch (error) {
|
||||
setShowError(true)
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const contactInfo = [
|
||||
{
|
||||
icon: <Email sx={{ fontSize: 30, color: 'primary.main' }} />,
|
||||
title: t('info.email.title'),
|
||||
content: t('info.email.content'),
|
||||
action: 'mailto:contact@biblical-guide.com'
|
||||
},
|
||||
{
|
||||
icon: <LocationOn sx={{ fontSize: 30, color: 'primary.main' }} />,
|
||||
title: t('info.address.title'),
|
||||
content: t('info.address.content'),
|
||||
action: null
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Box sx={{ py: 4 }}>
|
||||
{/* Hero Section */}
|
||||
<Box
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #009688 0%, #00796B 100%)',
|
||||
color: 'white',
|
||||
py: 8,
|
||||
mb: 6,
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center' }}>
|
||||
<ContactSupport sx={{ fontSize: 80, mb: 2, opacity: 0.9 }} />
|
||||
<Typography variant="h2" component="h1" gutterBottom>
|
||||
{t('hero.title')}
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h2" sx={{ mb: 2, opacity: 0.9 }}>
|
||||
{t('hero.subtitle')}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ opacity: 0.8, maxWidth: 600, mx: 'auto' }}>
|
||||
{t('hero.description')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
</Box>
|
||||
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
{/* Contact Form */}
|
||||
<Box sx={{ flex: { xs: '1 1 100%', md: '1 1 65%' } }}>
|
||||
<Card sx={{ height: 'fit-content' }}>
|
||||
<CardContent sx={{ p: 4 }}>
|
||||
<Typography variant="h4" component="h2" gutterBottom>
|
||||
{t('form.title')}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
{t('form.description')}
|
||||
</Typography>
|
||||
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
label={t('form.fields.name')}
|
||||
value={formData.name}
|
||||
onChange={handleInputChange('name')}
|
||||
variant="outlined"
|
||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 calc(50% - 8px)' } }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
type="email"
|
||||
label={t('form.fields.email')}
|
||||
value={formData.email}
|
||||
onChange={handleInputChange('email')}
|
||||
variant="outlined"
|
||||
sx={{ flex: { xs: '1 1 100%', sm: '1 1 calc(50% - 8px)' } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
label={t('form.fields.subject')}
|
||||
value={formData.subject}
|
||||
onChange={handleInputChange('subject')}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
multiline
|
||||
rows={6}
|
||||
label={t('form.fields.message')}
|
||||
value={formData.message}
|
||||
onChange={handleInputChange('message')}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<Box>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
startIcon={<Send />}
|
||||
sx={{ minWidth: 200 }}
|
||||
>
|
||||
{isSubmitting ? t('form.submitting') : t('form.submit')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{/* Contact Information */}
|
||||
<Box sx={{ flex: { xs: '1 1 100%', md: '1 1 35%' } }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
<Typography variant="h4" component="h2">
|
||||
{t('info.title')}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||
{t('info.description')}
|
||||
</Typography>
|
||||
|
||||
{contactInfo.map((info, index) => (
|
||||
<Paper
|
||||
key={index}
|
||||
sx={{
|
||||
p: 3,
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 2,
|
||||
cursor: info.action ? 'pointer' : 'default',
|
||||
transition: 'transform 0.2s ease-in-out',
|
||||
'&:hover': info.action ? {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: 2
|
||||
} : {}
|
||||
}}
|
||||
onClick={() => info.action && window.open(info.action, '_self')}
|
||||
>
|
||||
<Box sx={{ flexShrink: 0 }}>
|
||||
{info.icon}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{info.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{info.content}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
))}
|
||||
|
||||
{/* FAQ Quick Link */}
|
||||
<Paper sx={{ p: 3, bgcolor: 'primary.light', color: 'white' }}>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
{t('faq.title')}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 3, opacity: 0.9 }}>
|
||||
{t('faq.description')}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderColor: 'white',
|
||||
'&:hover': {
|
||||
borderColor: 'white',
|
||||
bgcolor: 'rgba(255,255,255,0.1)'
|
||||
}
|
||||
}}
|
||||
onClick={() => router.push(`/${locale}#faq`)}
|
||||
>
|
||||
{t('faq.viewFaq')}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
|
||||
{/* Success/Error Messages */}
|
||||
<Snackbar
|
||||
open={showSuccess}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setShowSuccess(false)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert onClose={() => setShowSuccess(false)} severity="success" sx={{ width: '100%' }}>
|
||||
{t('form.success')}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
|
||||
<Snackbar
|
||||
open={showError}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setShowError(false)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert onClose={() => setShowError(false)} severity="error" sx={{ width: '100%' }}>
|
||||
{t('form.error')}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user