127 lines
5.3 KiB
TypeScript
127 lines
5.3 KiB
TypeScript
|
|
import { useSubmissionForm } from "../features/submission/useSubmissionForm";
|
||
|
|
|
||
|
|
export const SubmissionRoute = () => {
|
||
|
|
const { state, progress, status, statusTone, submitting, updateField, submit } = useSubmissionForm();
|
||
|
|
|
||
|
|
return (
|
||
|
|
<main className="submission-shell">
|
||
|
|
<div className="submission-stage">
|
||
|
|
<section className="submission-hero">
|
||
|
|
<p className="submission-kicker">Good Grief</p>
|
||
|
|
<h1 className="submission-title">Offer a photo to tonight's memory field.</h1>
|
||
|
|
<p className="submission-copy">
|
||
|
|
Share one image that carries memory, witness, humor, or tenderness for you. The creative team will
|
||
|
|
review each submission. Not every image will appear, and none will be shown without moderation.
|
||
|
|
</p>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section className="submission-card">
|
||
|
|
<div className="submission-grid">
|
||
|
|
<div className="submission-field">
|
||
|
|
<label htmlFor="file">Photo</label>
|
||
|
|
<div className="submission-file">
|
||
|
|
<input
|
||
|
|
id="file"
|
||
|
|
type="file"
|
||
|
|
accept="image/jpeg,image/png,image/heic,image/heif"
|
||
|
|
onChange={(event) => updateField("file", event.target.files?.[0] ?? null)}
|
||
|
|
/>
|
||
|
|
<p className="submission-status">
|
||
|
|
One image only. Common phone photos work best. Unsupported files will be declined.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="submission-field">
|
||
|
|
<label htmlFor="displayName">Name or initials (optional)</label>
|
||
|
|
<input
|
||
|
|
id="displayName"
|
||
|
|
type="text"
|
||
|
|
value={state.displayName}
|
||
|
|
maxLength={80}
|
||
|
|
onChange={(event) => updateField("displayName", event.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="submission-field">
|
||
|
|
<label htmlFor="caption">Caption or note (optional)</label>
|
||
|
|
<textarea
|
||
|
|
id="caption"
|
||
|
|
rows={3}
|
||
|
|
maxLength={180}
|
||
|
|
placeholder="A short caption, dedication, or line of context."
|
||
|
|
value={state.caption}
|
||
|
|
onChange={(event) => updateField("caption", event.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="submission-field">
|
||
|
|
<label htmlFor="promptAnswer">Optional prompt</label>
|
||
|
|
<textarea
|
||
|
|
id="promptAnswer"
|
||
|
|
rows={4}
|
||
|
|
maxLength={240}
|
||
|
|
placeholder="What would you want this image to carry tonight?"
|
||
|
|
value={state.promptAnswer}
|
||
|
|
onChange={(event) => updateField("promptAnswer", event.target.value)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="submission-checkboxes">
|
||
|
|
<p className="submission-label">Consent</p>
|
||
|
|
<label className="submission-checkbox">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={state.hasRights}
|
||
|
|
onChange={(event) => updateField("hasRights", event.target.checked)}
|
||
|
|
/>
|
||
|
|
<span>I have the right to share this photo, and I understand it may be declined.</span>
|
||
|
|
</label>
|
||
|
|
<label className="submission-checkbox">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={state.allowProjection}
|
||
|
|
onChange={(event) => updateField("allowProjection", event.target.checked)}
|
||
|
|
/>
|
||
|
|
<span>I consent to this image being used in a live theatrical performance.</span>
|
||
|
|
</label>
|
||
|
|
<label className="submission-checkbox">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={state.acknowledgePublicPerformance}
|
||
|
|
onChange={(event) => updateField("acknowledgePublicPerformance", event.target.checked)}
|
||
|
|
/>
|
||
|
|
<span>I understand this is a public performance setting and projection is not guaranteed.</span>
|
||
|
|
</label>
|
||
|
|
<label className="submission-checkbox">
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
checked={state.allowArchive}
|
||
|
|
onChange={(event) => updateField("allowArchive", event.target.checked)}
|
||
|
|
/>
|
||
|
|
<span>Optional: you may retain this image briefly after the show for archive review.</span>
|
||
|
|
</label>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{submitting ? (
|
||
|
|
<div className="submission-progress" aria-hidden="true">
|
||
|
|
<span style={{ width: `${progress}%` }} />
|
||
|
|
</div>
|
||
|
|
) : null}
|
||
|
|
|
||
|
|
<div className="submission-actions">
|
||
|
|
<p className="submission-status" data-tone={statusTone}>
|
||
|
|
{status ??
|
||
|
|
"This flow is intentionally simple: one image, clear consent, moderated review, no public gallery."}
|
||
|
|
</p>
|
||
|
|
<button className="submission-button" type="button" disabled={submitting} onClick={() => void submit()}>
|
||
|
|
{submitting ? "Uploading..." : "Submit Photo"}
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</div>
|
||
|
|
</main>
|
||
|
|
);
|
||
|
|
};
|