mirror of
https://github.com/wahyd4/heygo.git
synced 2026-08-08 21:05:48 +10:00
105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
import { useState } from 'react';
|
||
import { useTranslation } from 'react-i18next';
|
||
import type { PromotionSubmission } from '../lib/api';
|
||
|
||
interface ResubmitDialogProps {
|
||
readonly submission: PromotionSubmission;
|
||
readonly onSubmit: (input: { privateLinkId: string; proposedAlias: string; note?: string }) => Promise<void>;
|
||
readonly onClose: () => void;
|
||
}
|
||
|
||
export default function ResubmitDialog({ submission, onSubmit, onClose }: ResubmitDialogProps) {
|
||
const { t } = useTranslation();
|
||
const [proposedAlias, setProposedAlias] = useState<string>(submission.proposedAlias);
|
||
const [note, setNote] = useState<string>(submission.note ?? '');
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [submitting, setSubmitting] = useState(false);
|
||
|
||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||
event.preventDefault();
|
||
if (submitting) {
|
||
return;
|
||
}
|
||
setError(null);
|
||
setSubmitting(true);
|
||
try {
|
||
await onSubmit({
|
||
privateLinkId: submission.privateLinkId,
|
||
proposedAlias: proposedAlias.trim(),
|
||
note: note.trim() ? note.trim() : undefined,
|
||
});
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : 'Failed to resubmit promotion');
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="dialog-backdrop" role="dialog" aria-modal="true" aria-label={t('resubmitDialog.dialogLabel')}>
|
||
<form className="dialog-card link-form" onSubmit={handleSubmit}>
|
||
<div className="dialog-header">
|
||
<h2>{t('resubmitDialog.title')}</h2>
|
||
<button type="button" className="dialog-close" aria-label={t('resubmitDialog.closeLabel')} onClick={onClose} disabled={submitting}>×</button>
|
||
</div>
|
||
|
||
<p className="muted">
|
||
Resubmitting <span className="alias-pill">#{submission.privateLinkAlias ?? submission.privateLinkId}</span>
|
||
{submission.privateLinkTargetUrl ? <> → <span className="truncate">{submission.privateLinkTargetUrl}</span></> : null}
|
||
</p>
|
||
|
||
{submission.rejectionReason ? (
|
||
<div className="resubmit-reason" role="note">
|
||
<strong>{t('resubmitDialog.requestedChanges')}</strong>
|
||
<p>{submission.rejectionReason}</p>
|
||
</div>
|
||
) : null}
|
||
|
||
<div className="field">
|
||
<label htmlFor="resubmit-alias">{t('resubmitDialog.fieldAlias')}</label>
|
||
<div className="alias-input">
|
||
<span className="alias-input__prefix" aria-hidden="true">#</span>
|
||
<input
|
||
id="resubmit-alias"
|
||
name="proposedAlias"
|
||
value={proposedAlias}
|
||
onChange={(e) => setProposedAlias(e.target.value)}
|
||
required
|
||
maxLength={100}
|
||
autoComplete="off"
|
||
placeholder={t('resubmitDialog.aliasPlaceholder')}
|
||
disabled={submitting}
|
||
/>
|
||
</div>
|
||
<small className="hint alias-preview">
|
||
<span>{t('resubmitDialog.aliasHint')}</span>
|
||
{proposedAlias.trim() ? <span className="alias-pill">#{proposedAlias.trim()}</span> : null}
|
||
</small>
|
||
</div>
|
||
|
||
<div className="field">
|
||
<label htmlFor="resubmit-note">{t('resubmitDialog.fieldNote')}</label>
|
||
<textarea
|
||
id="resubmit-note"
|
||
name="note"
|
||
value={note}
|
||
onChange={(e) => setNote(e.target.value)}
|
||
rows={3}
|
||
maxLength={1000}
|
||
placeholder={t('resubmitDialog.notePlaceholder')}
|
||
disabled={submitting}
|
||
/>
|
||
</div>
|
||
|
||
{error ? <p className="form-error" role="alert">{error}</p> : null}
|
||
|
||
<div className="form-actions">
|
||
<button type="submit" disabled={submitting || !proposedAlias.trim()}>
|
||
{submitting ? t('resubmitDialog.submitting') : t('resubmitDialog.resubmitForReview')}
|
||
</button>
|
||
<button type="button" onClick={onClose} disabled={submitting}>{t('resubmitDialog.cancel')}</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
);
|
||
} |