// Owner Onboarding — 5-step pet-owner registration flow.
// Reuses the shared register field helpers but with owner-side content.
const Field = window.RegField;
const inputCss = window.regInputCss;
function OwnerRegister({ initialStep = 0 }) {
const [step, setStep] = React.useState(initialStep);
const [form, setForm] = React.useState({
pets: [
{ name: '', species: 'dog', breed: '', age: '', sex: '',
weight: '', traits: [],
allergies: '', meds: '',
likes: '' },
],
services: ['visit'],
frequency: 'occasional',
keyMethod: 'smart-lock',
reportFreq: 'every-visit',
emergency: '',
});
const set = (k,v) => setForm(f => ({...f, [k]:v}));
const toggle = (k,v) => setForm(f => ({...f, [k]: f[k].includes(v) ? f[k].filter(x=>x!==v) : [...f[k],v]}));
const updatePet = (i,patch) => setForm(f => ({...f, pets: f.pets.map((p,idx)=>idx===i?{...p,...patch}:p)}));
const togglePetTrait = (i,trait) => setForm(f => ({...f, pets: f.pets.map((p,idx)=>idx===i?{...p, traits: p.traits.includes(trait)?p.traits.filter(t=>t!==trait):[...p.traits,trait]}:p)}));
const steps = [
['ようこそ','01'],
['ペットの基本情報','02'],
['性格・健康','03'],
['希望のお世話','04'],
['お留守番の希望','05'],
];
const next = () => setStep(s => Math.min(s+1, steps.length-1));
const prev = () => setStep(s => Math.max(s-1, 0));
// 送信状態
const [submitState, setSubmitState] = React.useState({ phase:'idle', message:'' });
const submit = async () => {
setSubmitState({ phase:'loading', message:'送信中…' });
const p = form.pets[0] || {};
const data = {
ownerName: form.ownerName || '',
email: form.email || '',
phone: form.phone || '',
address: form.address || '',
petName: p.name || '',
petKind: p.species || '',
petAge: p.age || '',
petBreed: p.breed || '',
petWeight: p.weight || '',
petTemperament: (p.traits || []).join('・'),
petHealth: [p.allergies && `アレルギー: ${p.allergies}`, p.meds && `投薬: ${p.meds}`].filter(Boolean).join(' / '),
services: form.services || [],
reportFreq: form.frequency || '',
keyHandling: form.keyMethod || '',
reportFreqDetail: form.reportFreq || '',
emergency: form.emergency || '',
};
const res = await window.palpetSubmitRegistration({
type: 'customer',
data,
photos: [
['petPhoto', p.photo],
],
});
if (res.ok) {
setSubmitState({
phase:'success',
message: res.demo
? 'デモ送信が完了しました(実際の通知は走っていません)。次は「シッターを探す」へどうぞ。'
: `登録完了。受付ID: ${res.row?.id || '—'}。コンシェルジュより1〜2営業日でご案内します。`,
res,
});
} else {
setSubmitState({
phase:'error',
message: res.message || res.error || '送信に失敗しました。',
res,
});
}
};
return (
はじめまして! ペットの登録をお願いします
{/* Stepper rail */}
{/* Main */}
{step === 0 &&
}
{step === 1 &&
}
{step === 2 && }
{step === 3 && }
{step === 4 && }
{submitState.phase !== 'idle' && (() => {
const SF = window.SubmitFeedback;
return SF ? setSubmitState({phase:'idle',message:''})}/> : null;
})()}
{/* Pet preview */}
);
}
// ----- step bodies -----
function OwnerWelcome() {
return (
);
}
// ペット写真用の縦型アップローダー(PhotoUpload の vertical 変形)
function PetPhotoUpload({ value, onChange }) {
const inputRef = React.useRef(null);
const [previewURL, setPreviewURL] = React.useState(null);
React.useEffect(() => {
if (value instanceof File) {
const url = URL.createObjectURL(value);
setPreviewURL(url);
return () => URL.revokeObjectURL(url);
} else {
setPreviewURL(null);
}
}, [value]);
const pick = () => inputRef.current?.click();
const onFile = (e) => {
const f = e.target.files?.[0];
if (f) onChange(f);
e.target.value = '';
};
return (
{previewURL ? (

) : (
📷 photo
)}
);
}
function OwnerPet({form, updatePet}) {
const p = form.pets[0];
return (
STEP 02
ペットの基本情報
あとで何頭でも追加できます。
updatePet(0,{photo:f})}/>
お顔がはっきり見える写真を選んでください
updatePet(0,{name:e.target.value})}/>
{[['dog','犬'],['cat','猫'],['rabbit','うさぎ'],['other','他']].map(([k,n])=>{
const sel = p.species === k;
return (
);
})}
updatePet(0,{breed:e.target.value})}/>
updatePet(0,{age:+e.target.value})}/>
{[['F','メス'],['M','オス']].map(([k,n])=>{
const sel = p.sex === k;
return (
);
})}
updatePet(0,{weight:+e.target.value})}/>
);
}
function OwnerHealth({form, updatePet, togglePetTrait}) {
const p = form.pets[0];
const traits = [
['friendly','人懐っこい'],
['shy_strangers','人見知り'],
['active','元気いっぱい'],
['calm','おとなしい'],
['barks','よく吠える'],
['food_motivated','食いしん坊'],
['other_pets_ok','他の動物OK'],
['kids_ok','子ども慣れ'],
['anxious_alone','留守番が苦手'],
];
return (
STEP 03
{p.name ? `${p.name}ちゃんのこと、教えてください` : 'ペットのこと、教えてください'}
シッターさんがその子に合わせて、寄り添ってお世話できるように。
性格・特徴(複数選択可)
{traits.map(([k,n])=>{
const sel = p.traits.includes(k);
return (
);
})}
updatePet(0,{allergies:e.target.value})}/>
updatePet(0,{meds:e.target.value})}/>
);
}
function OwnerService({form, set, toggle}) {
const types = [
['visit','訪問ケア','飼い主様宅にお伺いします',Icons.Pin],
['walk','お散歩のみ','お家までお迎えに行きます',Icons.Sparkle],
['stay','お預かり(シッター宅)','シッターの自宅でお預かり',Icons.Heart],
['overnight','お泊まり','宿泊を含む長時間のケア',Icons.Calendar],
];
const freqs = [
['occasional','たまに(旅行・出張時)'],
['weekly','週1〜2回'],
['daily','毎日(平日 日中)'],
];
return (
STEP 04
どんなお世話を希望されますか?
あとから依頼ごとに変更できます。
依頼したいサービス(複数選択可)
{types.map(([k,n,d,I])=>{
const sel = form.services.includes(k);
return (
);
})}
頻度
{freqs.map(([k,n])=>{
const sel = form.frequency === k;
return (
);
})}
);
}
function OwnerCare({form, set}) {
const keys = [
['smart-lock','スマートロック','一時的なパスコードを発行'],
['hand-over','対面で受け渡し','初回打ち合わせ時に',],
['locker','宅配ボックス','コインロッカーなど'],
['hidden','ご自宅の隠し場所','詳細はチャットで'],
];
const reports = [
['every-visit','毎訪問のあと','写真付き / 散歩ルート付き'],
['daily','1日1回まとめて',''],
['emergency','緊急時のみ',''],
];
return (
STEP 05
お留守番中のご希望
シッターさんとの連絡や、鍵の受け渡し方法を選んでください。
鍵の受け渡し方法
{keys.map(([k,n,d])=>{
const sel = form.keyMethod === k;
return (
);
})}
レポートの頻度
{reports.map(([k,n,d])=>{
const sel = form.reportFreq === k;
return (
);
})}
set('emergency',e.target.value)}/>
ご登録内容は運営の管理用スプレッドシートに自動で記録され、Palpet運営宛に通知メールが届きます。
1〜2営業日でコンシェルジュより、初回カウンセリングのご案内をお送りします。
);
}
// ----- right preview -----
function PetPreviewCard({pet}) {
const speciesLabel = {dog:'犬',cat:'猫',rabbit:'うさぎ',other:'その他'};
const traitLabel = {
friendly:'人懐っこい',shy_strangers:'人見知り',active:'元気',calm:'おとなしい',
barks:'よく吠える',food_motivated:'食いしん坊',other_pets_ok:'動物OK',
kids_ok:'子ども慣れ',anxious_alone:'留守番苦手',
};
return (
{pet.name}
{pet.age}歳・{pet.sex==='F'?'♀':'♂'}
{speciesLabel[pet.species]} / {pet.breed} / {pet.weight}kg
{pet.traits.slice(0,4).map((t,i)=>(
{traitLabel[t]}
))}
{pet.allergies && (
⚠ {pet.allergies}
)}
);
}
window.OwnerRegister = OwnerRegister;