import type { TargetedEvent } from 'preact';
import { useState, useCallback } from 'preact/hooks';
import { BaseDialog } from './base-dialog';
import { FormInput, SubmitButton, StatusMessage } from './form-components';
import type { FormState, NotifySignupRequest, NotifySignupResponse } from './types';
interface NotifyMeDialogProps {
isOpen: boolean;
onClose: () => void;
productSlug: string;
productName?: string;
/** Optional custom API endpoint for testing */
apiEndpoint?: string;
/** Optional: custom portal container for embedded rendering (e.g. Storybook) */
portalContainer?: HTMLElement | null;
}
/**
* NotifyMe Dialog Component
*
* Dialog for signing up for product restock notifications.
* Single field: email address
*
* Features:
* - Email validation
* - Loading state during submission
* - Success/error feedback
* - Accessible form
*/
export const NotifyMeDialog = ({
isOpen,
onClose,
productSlug,
productName,
apiEndpoint = '/api/notify-signup',
portalContainer,
}: NotifyMeDialogProps) => {
const [email, setEmail] = useState('');
const [formState, setFormState] = useState<FormState>({ status: 'idle' });
const handleSubmit = useCallback(
async (e: TargetedEvent<HTMLFormElement>) => {
e.preventDefault();
if (!email.trim()) return;
setFormState({ status: 'submitting' });
try {
const requestBody: NotifySignupRequest = {
email: email.trim(),
productSlug,
};
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestBody),
});
if (response.status >= 500) {
setFormState({
status: 'error',
errorMessage: 'サーバーエラーが発生しました。もう一度お試しください。',
});
return;
}
const data: NotifySignupResponse = await response.json();
if (data.success) {
setFormState({ status: 'success' });
setEmail('');
} else {
setFormState({
status: 'error',
errorMessage:
data.error || data.message || '登録に失敗しました。もう一度お試しください。',
});
}
} catch {
setFormState({
status: 'error',
errorMessage: 'ネットワークエラーが発生しました。もう一度お試しください。',
});
}
},
[email, productSlug, apiEndpoint],
);
const handleClose = useCallback(() => {
// Reset form state when closing
setFormState({ status: 'idle' });
setEmail('');
onClose();
}, [onClose]);
const isSubmitting = formState.status === 'submitting';
const isSuccess = formState.status === 'success';
const isError = formState.status === 'error';
return (
<BaseDialog
isOpen={isOpen}
onClose={handleClose}
title="入荷通知を受け取る"
ariaDescribedBy="notify-me-description"
portalContainer={portalContainer}
>
<div id="notify-me-description" className="pb-vgap-sm text-zd-white">
{productName ? (
<p>
<span className="font-bold">{productName}</span>
の入荷時にメールでお知らせします。
</p>
) : (
<p>商品の入荷時にメールでお知らせします。</p>
)}
</div>
{isSuccess ? (
<StatusMessage type="success">
登録が完了しました!
<br />
入荷時にメールでお知らせいたします。
</StatusMessage>
) : (
<form onSubmit={handleSubmit}>
<FormInput
id="notify-me-email"
label="メールアドレス"
type="email"
value={email}
onChange={(e) => setEmail(e.currentTarget.value)}
placeholder="example@example.com"
required
disabled={isSubmitting}
autoComplete="email"
autoFocus
/>
{isError && (
<div className="pb-vgap-sm">
<StatusMessage type="error">{formState.errorMessage}</StatusMessage>
</div>
)}
<div className="pt-vgap-xs">
<SubmitButton disabled={!email.trim()} isLoading={isSubmitting}>
通知を受け取る
</SubmitButton>
</div>
</form>
)}
</BaseDialog>
);
};
export default NotifyMeDialog;