feat(tracker-web): roadmap submit + email-prompt modals via shared Modal (UX-3.2)

Replace the bespoke local Modal (and its slate/blue/white chrome) with the
shared @bytelyst/ui Modal (Radix dialog — focus-trap/Esc/scroll-lock) for
both the Submit Idea and vote email-prompt dialogs. The dialog titles
become the accessible heading; form fields move to Input/Select/Textarea
and the submit-result message to AlertBanner. Behaviour preserved.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-29 06:46:39 -07:00
parent f612d2ecd1
commit 51c30ed73d

View File

@ -6,6 +6,8 @@ import {
Button, Button,
Input, Input,
Select, Select,
Textarea,
Modal,
Badge, Badge,
StatusDot, StatusDot,
MetricCard, MetricCard,
@ -352,127 +354,118 @@ export default function RoadmapPage() {
</main> </main>
{/* Email prompt modal */} {/* Email prompt modal */}
{showEmailPrompt && ( <Modal
<Modal open={showEmailPrompt}
onClose={() => { onOpenChange={open => {
if (!open) {
setShowEmailPrompt(false); setShowEmailPrompt(false);
setPendingVoteId(null); setPendingVoteId(null);
}} }
> }}
<h3 className="text-lg font-semibold mb-2">Enter your email to vote</h3> title="Enter your email to vote"
<p className="text-sm text-slate-500 mb-4"> description="We use your email to track your votes. One vote per item."
We use your email to track your votes. One vote per item. size="sm"
</p> >
<input <div className="space-y-4">
<Input
type="email" type="email"
aria-label="Email"
placeholder="you@example.com" placeholder="you@example.com"
value={voteEmail} value={voteEmail}
onChange={e => setVoteEmail(e.target.value)} onChange={e => setVoteEmail(e.target.value)}
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm mb-4"
onKeyDown={e => e.key === 'Enter' && handleEmailSubmit()} onKeyDown={e => e.key === 'Enter' && handleEmailSubmit()}
autoFocus autoFocus
/> />
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<button <Button
variant="ghost"
onClick={() => { onClick={() => {
setShowEmailPrompt(false); setShowEmailPrompt(false);
setPendingVoteId(null); setPendingVoteId(null);
}} }}
className="px-4 py-2 text-sm text-slate-500 hover:text-slate-700"
> >
Cancel Cancel
</button> </Button>
<button <Button onClick={handleEmailSubmit}>Vote</Button>
onClick={handleEmailSubmit}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-sm font-medium"
>
Vote
</button>
</div> </div>
</Modal> </div>
)} </Modal>
{/* Submit modal */} {/* Submit modal */}
{showSubmit && ( <Modal
<Modal open={showSubmit}
onClose={() => { onOpenChange={open => {
if (!open) {
setShowSubmit(false); setShowSubmit(false);
setSubmitSuccess(''); setSubmitSuccess('');
}} }
> }}
<h3 className="text-lg font-semibold mb-4">Submit an Idea</h3> title="Submit an Idea"
{submitSuccess ? ( >
<div {submitSuccess ? (
className={`p-3 rounded-lg text-sm mb-4 ${submitSuccess.startsWith('Error') ? 'bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-300' : 'bg-green-50 text-green-700 dark:bg-green-900/20 dark:text-green-300'}`} <AlertBanner
> tone={submitSuccess.startsWith('Error') ? 'error' : 'success'}
{submitSuccess} className="mb-4"
</div> >
) : null} {submitSuccess}
<form onSubmit={handleSubmit} className="space-y-3"> </AlertBanner>
<div className="grid grid-cols-2 gap-3"> ) : null}
<input <form onSubmit={handleSubmit} className="space-y-3">
type="text" <div className="grid grid-cols-2 gap-3">
placeholder="Your name" <Input
value={submitForm.name}
onChange={e => setSubmitForm({ ...submitForm, name: e.target.value })}
required
className="px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm"
/>
<input
type="email"
placeholder="Your email"
value={submitForm.email}
onChange={e => setSubmitForm({ ...submitForm, email: e.target.value })}
required
className="px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm"
/>
</div>
<select
value={submitForm.type}
onChange={e => setSubmitForm({ ...submitForm, type: e.target.value })}
aria-label="Request type"
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm"
>
<option value="feature">Feature Request</option>
<option value="bug">Bug Report</option>
<option value="task">Task</option>
</select>
<input
type="text" type="text"
placeholder="Title — what would you like to see?" aria-label="Your name"
value={submitForm.title} placeholder="Your name"
onChange={e => setSubmitForm({ ...submitForm, title: e.target.value })} value={submitForm.name}
onChange={e => setSubmitForm({ ...submitForm, name: e.target.value })}
required required
maxLength={500}
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm"
/> />
<textarea <Input
placeholder="Describe your idea or issue in detail (optional)" type="email"
value={submitForm.description} aria-label="Your email"
onChange={e => setSubmitForm({ ...submitForm, description: e.target.value })} placeholder="Your email"
rows={4} value={submitForm.email}
maxLength={5000} onChange={e => setSubmitForm({ ...submitForm, email: e.target.value })}
className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-800 text-sm resize-none" required
/> />
<div className="flex justify-end gap-2 pt-2"> </div>
<button <Select
type="button" value={submitForm.type}
onClick={() => setShowSubmit(false)} onChange={e => setSubmitForm({ ...submitForm, type: e.target.value })}
className="px-4 py-2 text-sm text-slate-500 hover:text-slate-700" aria-label="Request type"
> options={[
Cancel { value: 'feature', label: 'Feature Request' },
</button> { value: 'bug', label: 'Bug Report' },
<button { value: 'task', label: 'Task' },
type="submit" ]}
disabled={submitting} />
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white rounded-lg text-sm font-medium" <Input
> type="text"
{submitting ? 'Submitting...' : 'Submit'} aria-label="Title"
</button> placeholder="Title — what would you like to see?"
</div> value={submitForm.title}
</form> onChange={e => setSubmitForm({ ...submitForm, title: e.target.value })}
</Modal> required
)} maxLength={500}
/>
<Textarea
aria-label="Description"
placeholder="Describe your idea or issue in detail (optional)"
value={submitForm.description}
onChange={e => setSubmitForm({ ...submitForm, description: e.target.value })}
rows={4}
maxLength={5000}
/>
<div className="flex justify-end gap-2 pt-2">
<Button type="button" variant="ghost" onClick={() => setShowSubmit(false)}>
Cancel
</Button>
<Button type="submit" loading={submitting}>
{submitting ? 'Submitting...' : 'Submit'}
</Button>
</div>
</form>
</Modal>
</div> </div>
); );
} }
@ -578,14 +571,3 @@ function ItemRow({
</div> </div>
); );
} }
function Modal({ children, onClose }: { children: React.ReactNode; onClose: () => void }) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onClose} />
<div className="relative bg-white dark:bg-slate-800 rounded-2xl shadow-2xl border border-slate-200 dark:border-slate-700 p-6 w-full max-w-md">
{children}
</div>
</div>
);
}