mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-04-25 15:43:08 -05:00
Make SubmitForm dummied out when not signed in
This commit is contained in:
parent
3e3a58354f
commit
d29ef3d3ed
|
|
@ -11,7 +11,7 @@ export default async function SubmitPage() {
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Submit your ROM hack</h1>
|
<h1 className="text-3xl font-bold tracking-tight">Submit your ROM hack</h1>
|
||||||
<p className="mt-2 text-[15px] text-foreground/80">Share your hack so others can discover and play it.</p>
|
<p className="mt-2 text-[15px] text-foreground/80">Share your hack so others can discover and play it.</p>
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<SubmitForm />
|
<SubmitForm dummy={!user} />
|
||||||
</div>
|
</div>
|
||||||
{!user && <SubmitAuthOverlay />}
|
{!user && <SubmitAuthOverlay />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ function SortableCoverItem({ id, index, url, onRemove }: { id: string; index: nu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SubmitForm() {
|
interface SubmitFormProps {
|
||||||
|
dummy?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SubmitForm({ dummy = false }: SubmitFormProps) {
|
||||||
const MAX_COVERS = 10;
|
const MAX_COVERS = 10;
|
||||||
const [title, setTitle] = React.useState("");
|
const [title, setTitle] = React.useState("");
|
||||||
const [author, setAuthor] = React.useState("");
|
const [author, setAuthor] = React.useState("");
|
||||||
|
|
@ -180,48 +184,87 @@ export default function SubmitForm() {
|
||||||
patchUrl: "",
|
patchUrl: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDummy = !!dummy;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-8 lg:grid-cols-[1fr_.9fr]">
|
<div className="grid gap-8 lg:grid-cols-[1fr_.9fr]">
|
||||||
<div>
|
<div>
|
||||||
<form className="grid gap-5">
|
<form className="grid gap-5">
|
||||||
|
{/* Title */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Title</label>
|
<label className="text-sm text-foreground/80">Title</label>
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={title}
|
<input
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
value={title}
|
||||||
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
/>
|
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
role="textbox"
|
||||||
|
aria-disabled
|
||||||
|
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none"
|
||||||
|
>
|
||||||
|
Your hack title
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mt-1 text-xs text-foreground/60">URL preview: <span className="text-foreground/80">/hack/{slug || "your-title"}</span></div>
|
<div className="mt-1 text-xs text-foreground/60">URL preview: <span className="text-foreground/80">/hack/{slug || "your-title"}</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Author */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Author</label>
|
<label className="text-sm text-foreground/80">Author</label>
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={author}
|
<input
|
||||||
onChange={(e) => setAuthor(e.target.value)}
|
value={author}
|
||||||
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
onChange={(e) => setAuthor(e.target.value)}
|
||||||
/>
|
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">Your name</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Summary */}
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm text-foreground/80">Short summary</label>
|
<label className="text-sm text-foreground/80">Short summary</label>
|
||||||
<span className={`text-[11px] ${summaryTooLong ? "text-red-300" : "text-foreground/60"}`}>{summary.length}/{summaryLimit}</span>
|
<span className={`text-[11px] ${summaryTooLong ? "text-red-300" : "text-foreground/60"}`}>{summary.length}/{summaryLimit}</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={summary}
|
<input
|
||||||
onChange={(e) => setSummary(e.target.value)}
|
value={summary}
|
||||||
placeholder="<= 100 characters"
|
onChange={(e) => setSummary(e.target.value)}
|
||||||
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${summaryTooLong ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
placeholder="<= 100 characters"
|
||||||
/>
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${summaryTooLong ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
role="textbox"
|
||||||
|
aria-disabled
|
||||||
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${summaryTooLong ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
|
>
|
||||||
|
Short description, max 100 characters.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Long description */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-sm text-foreground/80">Long description</label>
|
<label className="text-sm text-foreground/80">Long description</label>
|
||||||
<div className="flex items-center gap-1 text-xs">
|
{!isDummy && (
|
||||||
<button type="button" onClick={() => setShowMdPreview(false)} className={`px-2 py-1 rounded ${!showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Write</button>
|
<div className="flex items-center gap-1 text-xs">
|
||||||
<button type="button" onClick={() => setShowMdPreview(true)} className={`px-2 py-1 rounded ${showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Preview</button>
|
<button type="button" onClick={() => setShowMdPreview(false)} className={`px-2 py-1 rounded ${!showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Write</button>
|
||||||
</div>
|
<button type="button" onClick={() => setShowMdPreview(true)} className={`px-2 py-1 rounded ${showMdPreview ? "bg-[var(--surface-2)] ring-1 ring-[var(--border)]" : "text-foreground/70"}`}>Preview</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!showMdPreview ? (
|
{isDummy ? (
|
||||||
|
<div className="prose max-w-none h-36 rounded-md bg-[var(--surface-2)] px-3 py-2 ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none">
|
||||||
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{description || "Write a longer markdown description here."}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
) : !showMdPreview ? (
|
||||||
<textarea
|
<textarea
|
||||||
rows={6}
|
rows={6}
|
||||||
value={description}
|
value={description}
|
||||||
|
|
@ -235,15 +278,23 @@ export default function SubmitForm() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Version */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Version</label>
|
<label className="text-sm text-foreground/80">Version</label>
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={version}
|
<input
|
||||||
onChange={(e) => setVersion(e.target.value)}
|
value={version}
|
||||||
placeholder="e.g. v1.2.0"
|
onChange={(e) => setVersion(e.target.value)}
|
||||||
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
placeholder="e.g. v1.2.0"
|
||||||
/>
|
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div role="textbox" aria-disabled className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">v0.1.0</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Tags */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Tags</label>
|
<label className="text-sm text-foreground/80">Tags</label>
|
||||||
<div className="rounded-md ring-1 ring-inset ring-[var(--border)] bg-[var(--surface-2)] px-2 py-2">
|
<div className="rounded-md ring-1 ring-inset ring-[var(--border)] bg-[var(--surface-2)] px-2 py-2">
|
||||||
|
|
@ -251,18 +302,24 @@ export default function SubmitForm() {
|
||||||
{tags.map((t, i) => (
|
{tags.map((t, i) => (
|
||||||
<span key={`${t}-${i}`} className="inline-flex items-center gap-1 rounded-full bg-[var(--surface-2)] px-2 py-1 text-xs ring-1 ring-[var(--border)]">
|
<span key={`${t}-${i}`} className="inline-flex items-center gap-1 rounded-full bg-[var(--surface-2)] px-2 py-1 text-xs ring-1 ring-[var(--border)]">
|
||||||
{t}
|
{t}
|
||||||
<button type="button" onClick={() => removeTagAt(i)} className="ml-1 text-foreground/70 hover:text-foreground">×</button>
|
{!isDummy && (
|
||||||
|
<button type="button" onClick={() => removeTagAt(i)} className="ml-1 text-foreground/70 hover:text-foreground">×</button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={tagsInput}
|
<input
|
||||||
onChange={(e) => setTagsInput(e.target.value)}
|
value={tagsInput}
|
||||||
onKeyDown={onTagsKeyDown}
|
onChange={(e) => setTagsInput(e.target.value)}
|
||||||
placeholder={tags.length ? "Add tag" : "Add tags (e.g. QoL, Challenge)"}
|
onKeyDown={onTagsKeyDown}
|
||||||
className="flex-1 min-w-[8rem] bg-transparent px-2 text-sm placeholder:text-foreground/50 focus:outline-none"
|
placeholder={tags.length ? "Add tag" : "Add tags (e.g. QoL, Challenge)"}
|
||||||
/>
|
className="flex-1 min-w-[8rem] bg-transparent px-2 text-sm placeholder:text-foreground/50 focus:outline-none"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 min-w-[8rem] px-2 text-sm text-foreground/50 select-none">{tags.length ? "Add tag" : "Add tags (e.g. QoL, Challenge)"}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{suggestedTags.length > 0 && (
|
{!isDummy && suggestedTags.length > 0 && (
|
||||||
<div className="mt-2 flex flex-wrap gap-1">
|
<div className="mt-2 flex flex-wrap gap-1">
|
||||||
{suggestedTags.map((s) => (
|
{suggestedTags.map((s) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -278,32 +335,53 @@ export default function SubmitForm() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Cover images */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Cover images</label>
|
<label className="text-sm text-foreground/80">Cover images</label>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<textarea
|
{!isDummy ? (
|
||||||
rows={2}
|
<textarea
|
||||||
value={newCoversInput}
|
rows={2}
|
||||||
onChange={(e) => setNewCoversInput(e.target.value)}
|
value={newCoversInput}
|
||||||
placeholder="Paste one or multiple URLs (comma or newline separated)"
|
onChange={(e) => setNewCoversInput(e.target.value)}
|
||||||
className="w-full rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
placeholder="Paste one or multiple URLs (comma or newline separated)"
|
||||||
/>
|
className="w-full rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-14 rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm ring-1 ring-inset ring-[var(--border)] text-foreground/60 select-none">
|
||||||
|
Paste one or multiple URLs (comma or newline separated)
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
{!isDummy ? (
|
||||||
type="button"
|
<>
|
||||||
onClick={addFromInput}
|
<button
|
||||||
disabled={coverUrls.length >= MAX_COVERS}
|
type="button"
|
||||||
className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed"
|
onClick={addFromInput}
|
||||||
>
|
disabled={coverUrls.length >= MAX_COVERS}
|
||||||
Add
|
className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
</button>
|
>
|
||||||
<button
|
Add
|
||||||
type="button"
|
</button>
|
||||||
onClick={() => setNewCoversInput("")}
|
<button
|
||||||
className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/80 transition-colors hover:bg-black/5 dark:hover:bg-white/10"
|
type="button"
|
||||||
>
|
onClick={() => setNewCoversInput("")}
|
||||||
Clear
|
className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/80 transition-colors hover:bg-black/5 dark:hover:bg-white/10"
|
||||||
</button>
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button type="button" disabled className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/70 disabled:opacity-40">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
<button type="button" disabled className="inline-flex h-9 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-3 text-xs font-medium text-foreground/60 disabled:opacity-40">
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-foreground/60 flex justify-between">
|
<div className="text-xs text-foreground/60 flex justify-between">
|
||||||
<p>Images: <span className={overLimit ? "text-red-300 font-bold" : "text-foreground/60"}>{coverUrls.length}</span>/{MAX_COVERS}</p>
|
<p>Images: <span className={overLimit ? "text-red-300 font-bold" : "text-foreground/60"}>{coverUrls.length}</span>/{MAX_COVERS}</p>
|
||||||
|
|
@ -313,105 +391,146 @@ export default function SubmitForm() {
|
||||||
{coverUrls.length === 0 ? (
|
{coverUrls.length === 0 ? (
|
||||||
<p className="text-xs text-foreground/60">No images added yet. Add at least one to preview.</p>
|
<p className="text-xs text-foreground/60">No images added yet. Add at least one to preview.</p>
|
||||||
) : (
|
) : (
|
||||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
!isDummy ? (
|
||||||
<SortableContext
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
||||||
items={coverUrls.map((url, i) => `${url}-${i}`)}
|
<SortableContext
|
||||||
strategy={verticalListSortingStrategy}
|
items={coverUrls.map((url, i) => `${url}-${i}`)}
|
||||||
>
|
strategy={verticalListSortingStrategy}
|
||||||
|
>
|
||||||
|
{coverUrls.map((url, i) => (
|
||||||
|
<SortableCoverItem
|
||||||
|
key={`${url}-${i}`}
|
||||||
|
id={`${url}-${i}`}
|
||||||
|
index={i}
|
||||||
|
url={url}
|
||||||
|
onRemove={() => removeAt(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{coverUrls.map((url, i) => (
|
{coverUrls.map((url, i) => (
|
||||||
<SortableCoverItem
|
<StaticCoverItem key={`${url}-${i}`} index={i} url={url} />
|
||||||
key={`${url}-${i}`}
|
|
||||||
id={`${url}-${i}`}
|
|
||||||
index={i}
|
|
||||||
url={url}
|
|
||||||
onRemove={() => removeAt(i)}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</SortableContext>
|
</>
|
||||||
</DndContext>
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Base ROM */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Base ROM</label>
|
<label className="text-sm text-foreground/80">Base ROM</label>
|
||||||
<select
|
{!isDummy ? (
|
||||||
value={baseRom}
|
<select
|
||||||
onChange={(e) => setBaseRom(e.target.value)}
|
value={baseRom}
|
||||||
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
onChange={(e) => setBaseRom(e.target.value)}
|
||||||
>
|
className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-[var(--ring)]"
|
||||||
{baseRoms.map(({ name, region }) => (
|
>
|
||||||
<option key={name} value={name}>
|
{baseRoms.map(({ name, region }) => (
|
||||||
{name} ({region})
|
<option key={name} value={name}>
|
||||||
</option>
|
{name} ({region})
|
||||||
))}
|
</option>
|
||||||
</select>
|
))}
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<div className="h-11 rounded-md bg-[var(--surface-2)] px-3 text-sm ring-1 ring-inset ring-[var(--border)] flex items-center text-foreground/60 select-none">{baseRom}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Box art URL */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Box art URL <span className="text-foreground/60">(optional)</span></label>
|
<label className="text-sm text-foreground/80">Box art URL <span className="text-foreground/60">(optional)</span></label>
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={boxArt}
|
<input
|
||||||
onChange={(e) => setBoxArt(e.target.value)}
|
value={boxArt}
|
||||||
placeholder="https://..."
|
onChange={(e) => setBoxArt(e.target.value)}
|
||||||
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${boxArt && !urlLike(boxArt) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
placeholder="https://..."
|
||||||
/>
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${boxArt && !urlLike(boxArt) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${boxArt && !urlLike(boxArt) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>https://...</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Social links */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Social links <span className="text-foreground/60">(optional)</span></label>
|
<label className="text-sm text-foreground/80">Social links <span className="text-foreground/60">(optional)</span></label>
|
||||||
<div className="grid gap-2 sm:grid-cols-3">
|
<div className="grid gap-2 sm:grid-cols-3">
|
||||||
<input
|
{!isDummy ? (
|
||||||
value={discord}
|
<>
|
||||||
onChange={(e) => setDiscord(e.target.value)}
|
<input
|
||||||
placeholder="Discord invite URL"
|
value={discord}
|
||||||
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${discord && !urlLike(discord) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
onChange={(e) => setDiscord(e.target.value)}
|
||||||
/>
|
placeholder="Discord invite URL"
|
||||||
<input
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${discord && !urlLike(discord) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
value={twitter}
|
/>
|
||||||
onChange={(e) => setTwitter(e.target.value)}
|
<input
|
||||||
placeholder="Twitter/X profile URL"
|
value={twitter}
|
||||||
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${twitter && !urlLike(twitter) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
onChange={(e) => setTwitter(e.target.value)}
|
||||||
/>
|
placeholder="Twitter/X profile URL"
|
||||||
<input
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${twitter && !urlLike(twitter) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
value={pokecommunity}
|
/>
|
||||||
onChange={(e) => setPokecommunity(e.target.value)}
|
<input
|
||||||
placeholder="PokeCommunity thread URL"
|
value={pokecommunity}
|
||||||
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${pokecommunity && !urlLike(pokecommunity) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
onChange={(e) => setPokecommunity(e.target.value)}
|
||||||
/>
|
placeholder="PokeCommunity thread URL"
|
||||||
|
className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset focus:outline-none focus:ring-2 focus:ring-[var(--ring)] ${pokecommunity && !urlLike(pokecommunity) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${discord && !urlLike(discord) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>Discord invite URL</div>
|
||||||
|
<div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${twitter && !urlLike(twitter) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>Twitter/X profile URL</div>
|
||||||
|
<div className={`h-11 rounded-md px-3 text-sm ring-1 ring-inset flex items-center text-foreground/60 select-none ${pokecommunity && !urlLike(pokecommunity) ? "ring-red-600/40 bg-red-500/10 dark:ring-red-400/40 dark:bg-red-950/20" : "bg-[var(--surface-2)] ring-[var(--border)]"}`}>PokeCommunity thread URL</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-foreground/60">Use full URLs starting with http:// or https://</p>
|
<p className="text-xs text-foreground/60">Use full URLs starting with http:// or https://</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Upload patch file */}
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-sm text-foreground/80">Upload patch file</label>
|
<label className="text-sm text-foreground/80">Upload patch file</label>
|
||||||
<input type="file" className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm italic text-foreground/50 ring-1 ring-inset ring-[var(--border)] file:bg-black/10 dark:file:bg-[var(--surface-2)] file:text-foreground/80 file:text-sm file:font-medium file:not-italic file:rounded-md file:border-0 file:px-3 file:py-2 file:mr-2 file:cursor-pointer" />
|
{!isDummy ? (
|
||||||
|
<input type="file" className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm italic text-foreground/50 ring-1 ring-inset ring-[var(--border)] file:bg-black/10 dark:file:bg-[var(--surface-2)] file:text-foreground/80 file:text-sm file:font-medium file:not-italic file:rounded-md file:border-0 file:px-3 file:py-2 file:mr-2 file:cursor-pointer" />
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md bg-[var(--surface-2)] px-3 py-2 text-sm italic text-foreground/50 ring-1 ring-inset ring-[var(--border)] select-none">Choose file</div>
|
||||||
|
)}
|
||||||
<p className="text-xs text-foreground/60">BPS only for verification purposes.</p>
|
<p className="text-xs text-foreground/60">BPS only for verification purposes.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<button
|
{/* Submit button */}
|
||||||
type="button"
|
{!isDummy && (
|
||||||
disabled={!isValid}
|
<div className="flex items-center gap-3">
|
||||||
className="shine-wrap btn-premium h-11 min-w-[7.5rem] text-sm font-semibold dark:disabled:opacity-70 disabled:cursor-not-allowed disabled:[box-shadow:0_0_0_1px_var(--border)]"
|
<button
|
||||||
>
|
type="button"
|
||||||
<span>Submit</span>
|
disabled={!isValid}
|
||||||
</button>
|
className="shine-wrap btn-premium h-11 min-w-[7.5rem] text-sm font-semibold dark:disabled:opacity-70 disabled:cursor-not-allowed disabled:[box-shadow:0_0_0_1px_var(--border)]"
|
||||||
{!isValid && (
|
>
|
||||||
<span className="text-xs text-red-600/90">Fill required fields, fix errors, and add at least one cover.</span>
|
<span>Submit</span>
|
||||||
)}
|
</button>
|
||||||
</div>
|
{!isValid && (
|
||||||
|
<span className="text-xs text-red-600/90">Fill required fields, fix errors, and add at least one cover.</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="flex flex-col gap-5 lg:sticky lg:top-20 self-start">
|
<aside className="flex flex-col gap-5 lg:sticky lg:top-20 self-start">
|
||||||
<PreviewCard hack={preview} />
|
<PreviewCard hack={preview} />
|
||||||
|
<div className="card h-max p-5">
|
||||||
<div className="card h-max p-5">
|
<div className="text-[15px] font-semibold tracking-tight">Submission tips</div>
|
||||||
<div className="text-[15px] font-semibold tracking-tight">Submission tips</div>
|
<ul className="mt-3 list-disc space-y-2 pl-5 text-sm text-foreground/75">
|
||||||
<ul className="mt-3 list-disc space-y-2 pl-5 text-sm text-foreground/75">
|
<li>Use a reliable image URL (e.g. `imgur`).</li>
|
||||||
<li>Use a reliable image URL (e.g. `imgur`).</li>
|
<li>Include the exact expected base ROM name.</li>
|
||||||
<li>Include the exact expected base ROM name.</li>
|
<li>Describe notable features, difficulty, and target players.</li>
|
||||||
<li>Describe notable features, difficulty, and target players.</li>
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
</aside>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -420,4 +539,32 @@ function PreviewCard({ hack }: { hack: Hack }) {
|
||||||
return <HackCard hack={hack} clickable={false} />;
|
return <HackCard hack={hack} clickable={false} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StaticCoverItem({ index, url }: { index: number; url: string }) {
|
||||||
|
return (
|
||||||
|
<div className="rounded-md">
|
||||||
|
<div className="h-16 flex items-center justify-between gap-3 p-2 bg-[var(--surface-2)] ring-1 ring-inset ring-[var(--border)]">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="pr-1 text-foreground/40 select-none" title="Drag disabled">⋮⋮</div>
|
||||||
|
<div className="relative h-12 w-20 overflow-hidden rounded">
|
||||||
|
<Image src={url} alt={`Cover ${index + 1}`} fill className="object-cover" unoptimized />
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="truncate text-xs text-foreground/80">{url}</div>
|
||||||
|
{index === 0 && <div className="text-[10px] text-emerald-400/90">Primary</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled
|
||||||
|
className="inline-flex h-8 items-center justify-center rounded-md border border-[var(--border)] bg-[var(--surface-2)] px-2 text-xs text-foreground/50"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user