Files
Orchestrator/privat/CV/andreknie.de/src/pages/Resources.jsx
T

116 lines
6.0 KiB
React

import React, { useState } from 'react'
import { useContent } from '../hooks/useContent'
import { useFormSubmit } from '../hooks/useFormSubmit'
import { validateResourceDownload } from '../utils/validation'
import SEOHead from '../components/SEOHead'
const inputStyle = {
width: '100%',
padding: '12px',
background: 'var(--bg-elevated)',
border: '1px solid var(--glass-border)',
borderRadius: 'var(--radius-sm)',
color: 'var(--text-primary)',
fontSize: '1rem',
}
function ResourceRequestForm({ resource, onCancel }) {
const resourceId = resource.id || resource.slug
const [form, setForm] = useState({ name: '', email: '', company: '', resource_id: resourceId, company_website: '' })
const { submit, loading, success, errors, serverError } = useFormSubmit('/api/resource-download', validateResourceDownload)
function updateField(event) {
setForm(prev => ({ ...prev, [event.target.name]: event.target.value }))
}
async function handleSubmit(event) {
event.preventDefault()
await submit(form)
}
return (
<div className="glass-panel" style={{ padding: '24px', marginTop: '24px' }}>
{success ? (
<p role="status" aria-live="polite" style={{ margin: 0, color: 'var(--text-primary)' }}>
Danke! Wir prüfen die Anfrage und melden uns bei dir.
</p>
) : (
<form onSubmit={handleSubmit} noValidate>
<h2 style={{ fontSize: '1.25rem', marginBottom: '8px' }}>Material anfragen</h2>
<p style={{ color: 'var(--text-secondary)', marginTop: 0 }}>
Wir prüfen die Anfrage. Bei positiver Prüfung verschickt André ein aktuelles Profil persönlich.
</p>
<div hidden>
<label htmlFor="resource-company-website">Firmen-Website (bitte leer lassen)</label>
<input id="resource-company-website" name="company_website" tabIndex={-1} autoComplete="off" value={form.company_website} onChange={updateField} />
</div>
<input type="hidden" name="resource_id" value={resourceId} />
<label htmlFor="resource-name">Name</label>
<input id="resource-name" name="name" value={form.name} onChange={updateField} style={inputStyle} autoComplete="name" aria-invalid={Boolean(errors.name)} aria-describedby={errors.name ? 'resource-name-error' : undefined} />
{errors.name && <p id="resource-name-error" role="alert" style={{ color: '#ff4444', margin: '4px 0 12px' }}>{errors.name}</p>}
<label htmlFor="resource-email">E-Mail</label>
<input id="resource-email" name="email" type="email" value={form.email} onChange={updateField} style={inputStyle} autoComplete="email" aria-invalid={Boolean(errors.email)} aria-describedby={errors.email ? 'resource-email-error' : undefined} />
{errors.email && <p id="resource-email-error" role="alert" style={{ color: '#ff4444', margin: '4px 0 12px' }}>{errors.email}</p>}
<label htmlFor="resource-company">Unternehmen</label>
<input id="resource-company" name="company" value={form.company} onChange={updateField} style={inputStyle} autoComplete="organization" aria-invalid={Boolean(errors.company)} aria-describedby={errors.company ? 'resource-company-error' : undefined} />
{errors.company && <p id="resource-company-error" role="alert" style={{ color: '#ff4444', margin: '4px 0 12px' }}>{errors.company}</p>}
{serverError && <p role="alert" aria-live="assertive" style={{ color: '#ff4444' }}>{serverError}</p>}
{errors.resource_id && <p role="alert" style={{ color: '#ff4444' }}>{errors.resource_id}</p>}
<div style={{ display: 'flex', gap: '12px', marginTop: '20px', flexWrap: 'wrap' }}>
<button type="submit" className="primary-button" disabled={loading}>
{loading ? 'Wird gesendet...' : 'Anfrage senden'}
</button>
<button type="button" className="secondary-button" onClick={onCancel}>Abbrechen</button>
</div>
</form>
)}
</div>
)
}
export default function Resources() {
const { items } = useContent('resources', { pageSize: 20 })
const [selectedResource, setSelectedResource] = useState(null)
return (
<main style={{ paddingTop: '120px', minHeight: '100vh' }}>
<SEOHead title="Ressourcen" description="Ausgewählte Materialien und Profile von Dr. André Knie auf Anfrage." url="/resources" />
<div className="app-container">
<div className="section-header">
<h1 className="section-title"><span className="text-gradient">Ressourcen</span></h1>
<p className="section-subtitle">Materialien auf Anfrage. Jede Anfrage wird zunächst geprüft.</p>
</div>
<div style={{ display: 'grid', gap: '20px', maxWidth: '700px', margin: '0 auto' }}>
{items.map(resource => {
const resourceId = resource.id || resource.slug
const selected = selectedResource === resourceId
return (
<div key={resourceId} className="glass-panel" style={{ padding: '24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '16px', flexWrap: 'wrap' }}>
<div>
<h2 style={{ marginBottom: '4px', fontSize: '1.1rem' }}>{resource.title}</h2>
<p style={{ color: 'var(--text-secondary)', margin: 0, fontSize: '0.9rem' }}>{resource.description}</p>
</div>
<button type="button" className="primary-button" onClick={() => setSelectedResource(selected ? null : resourceId)} style={{ padding: '10px 20px', fontSize: '0.9rem', whiteSpace: 'nowrap' }}>
{selected ? 'Schließen' : 'Material anfragen'}
</button>
</div>
{selected && <ResourceRequestForm key={resourceId} resource={resource} onCancel={() => setSelectedResource(null)} />}
</div>
)
})}
{items.length === 0 && <p style={{ textAlign: 'center', color: 'var(--text-secondary)' }}>Ressourcen werden vorbereitet.</p>}
</div>
</div>
</main>
)
}