// Sitter Onboarding — 6-step registration flow.
// Interactive: stepper at top, prev/next, fields editable.
function SitterRegister({ initialStep = 0 }) {
const [step, setStep] = React.useState(initialStep);
const [form, setForm] = React.useState({
pets: ['dog'],
exp: 3,
skills: ['walk', 'med'],
serviceTypes: ['visit'],
rate: 3200,
days: ['mon','tue','wed','thu','fri'],
bio: '',
idVerified: true,
cert: 'pet-care-2',
area: '107-0062 港区南青山',
stations: ['表参道','外苑前'],
// 動物取扱責任者
licStatus: 'none', // 'have' | 'none'
licNumber: '',
licCategory: 'hokan', // 保管 / 訓練 / 展示
licPrefecture: '',
licHolderName: '',
licAgree: false, // ソプラ銀座との業務委託合意
// 写真
profilePhoto: null, // File | null
idDocument: null, // File | null(身分証)
selfie: null, // File | null
// 規約系
insAgree: false,
tosAgree: false,
});
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 steps = [
['基本情報','01'],
['対応ペット','02'],
['経験・資格','03'],
['サービス内容','04'],
['料金・空き状況','05'],
['プロフィール','06'],
['本人確認','07'],
];
const next = () => setStep(s => Math.min(s + 1, steps.length - 1));
const prev = () => setStep(s => Math.max(s - 1, 0));
// 送信状態:idle / loading / success / error
const [submitState, setSubmitState] = React.useState({ phase: 'idle', message: '' });
const submit = async () => {
setSubmitState({ phase: 'loading', message: '送信中…' });
// form → webhook data 形式へ整形(File ではない値のみ)
const data = {
lastName: form.lastName || '', firstName: form.firstName || '',
nickname: form.nickname || '',
birthDate: form.birthDate || '', gender: form.gender || '',
email: form.email || '', phone: form.phone || '',
area: form.area || '', stations: form.stations || [],
pets: form.pets || [], exp: form.exp || 0, cert: form.cert || '',
skills: form.skills || [], serviceTypes: form.serviceTypes || [],
rate: form.rate || 0, days: form.days || [], bio: form.bio || '',
licStatus: form.licStatus || 'none', licNumber: form.licNumber || '',
licCategory: form.licCategory || '', licPrefecture: form.licPrefecture || '',
licHolderName: form.licHolderName || '',
idVerified: !!(form.idDocument && form.selfie),
};
const res = await window.palpetSubmitRegistration({
type: 'sitter',
data,
photos: [
['profilePhoto', form.profilePhoto],
['idDocument', form.idDocument],
['selfie', form.selfie],
],
});
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 (
{/* Top nav */}
シッター登録 ・ 進捗を保存しました
{/* Stepper rail */}
{/* Main form area */}
{/* Progress bar */}
{step === 0 &&
}
{step === 1 && }
{step === 2 && }
{step === 3 && }
{step === 4 && }
{step === 5 && }
{step === 6 && }
{/* 送信状態フィードバック */}
{submitState.phase !== 'idle' && (
setSubmitState({phase:'idle',message:''})}/>
)}
{/* Preview rail */}
);
}
// ---------- Step bodies ----------
const SectionH = ({n, t, sub}) => (
STEP {n}
{t}
{sub &&
{sub}
}
);
const Field = ({label, hint, children, half}) => (
{label}
{children}
{hint &&
{hint}
}
);
const inputCss = {
width:'100%',padding:'14px 16px',borderRadius:14,
border:'1.5px solid rgba(0,0,0,.08)',background:'#fff',
fontSize:15,fontFamily:'inherit',color:'#2E2418',outline:'none',
};
function Step0({form, set, toggle}) {
return (<>
{['女性','男性','その他'].map((g,i)=>(
))}
set('area',e.target.value)}/>
toggle('stations',s)}/>
>);
}
// 駅選択 — エリアからのサジェスト + 自由入力で追加
function StationPicker({ stations, onToggle }) {
const [q, setQ] = React.useState('');
const suggestions = [
'表参道','外苑前','青山一丁目','広尾','六本木','乃木坂',
'赤坂','麻布十番','白金高輪','恵比寿','渋谷','原宿',
];
const filtered = q.trim()
? suggestions.filter(s => s.includes(q.trim()) && !stations.includes(s))
: suggestions.filter(s => !stations.includes(s));
const addCustom = () => {
const v = q.trim();
if (!v) return;
if (!stations.includes(v)) onToggle(v);
setQ('');
};
return (
{/* 選択済みチップ */}
{stations.length === 0 && (
まだ駅が選ばれていません
)}
{stations.map(s => (
{s}駅
))}
{/* 検索 + 追加 */}
setQ(e.target.value)}
onKeyDown={e=>{ if(e.key==='Enter'){ e.preventDefault(); addCustom(); }}}
placeholder="駅名で検索/追加(例:表参道)"
style={{
flex:1,padding:'10px 14px',borderRadius:10,
border:'1.5px solid rgba(0,0,0,.08)',background:'var(--c-bg)',
fontSize:14,fontFamily:'inherit',color:'#2E2418',outline:'none',
}}
/>
{/* サジェスト */}
このエリアの近隣駅
{filtered.length === 0 && (
候補がありません
)}
{filtered.map(s => (
))}
);
}
function Step1({form, toggle}) {
const pets = [
['dog','犬',PetGlyph.Dog,'小型〜大型'],
['cat','猫',PetGlyph.Cat,'室内・外猫'],
['rabbit','うさぎ',PetGlyph.Rabbit,'小動物全般'],
['bird','鳥','🐦','小鳥・インコ'],
['reptile','爬虫類','🦎','カメ・トカゲ'],
['fish','魚','🐟','給餌・水替え'],
];
return (<>
{pets.map(([k,n,G,d])=>{
const sel = form.pets.includes(k);
return (
);
})}
>);
}
function Step2({form, set, toggle}) {
const skills = [
['walk','散歩経験','長距離・複数頭OK'],
['med','投薬','錠剤・注射対応'],
['groom','ブラッシング',null],
['behavior','問題行動への対応',null],
['senior','シニアペットケア',null],
['kitten','子犬・子猫ケア',null],
];
return (<>
{/* ソプラアカデミー CTA — 資格なし or 経験未確認の人向け */}
{(form.cert === '' || form.cert === 'none') && (
)}
得意なお世話(複数選択可)
{skills.map(([k,n,sub])=>{
const sel = form.skills.includes(k);
return (
);
})}
{/* 動物取扱責任者 */}
動物取扱責任者の資格
ペットシッター業(保管)には動物取扱業の登録が必要です。お持ちかどうか教えてください。
{[
['have','登録あり','動物取扱責任者として登録済み'],
['none','登録なし','業務委託契約でカバーできます'],
].map(([k,n,d])=>{
const sel = form.licStatus === k;
return (
);
})}
{form.licStatus === 'have' && (
)}
{form.licStatus === 'none' && (
ソプラ銀座株式会社と業務委託契約を結びます
動物取扱責任者の資格をお持ちでないシッターは、運営元の
ソプラ銀座株式会社(動物取扱業登録:13東京都動セ第◯◯号/保管)と
業務委託契約を結ぶことで、合法的にお仕事ができます。手続きはオンラインで完結し、
登録費・年会費は無料です。
- 運営会社のスタッフとして、研修・サポートを受けられます
- 事故・損害保険は運営会社の包括契約でカバーされます
- 動物取扱責任者の資格取得を希望する場合、研修への参加もご案内します
{/* ソプラアカデミー CTA — 業務委託で進む人にも将来の資格取得を案内 */}
)}
>);
}
// ----------------- ソプラアカデミー CTA -----------------
function AcademyCTA({ compact, context }) {
const academy = (typeof window !== 'undefined' && window.SOPRA_ACADEMY);
if (!academy) return null;
const headline = context === 'license'
? '将来的に資格を取得して責任者になりたい方は'
: '資格がなくても、ソプラアカデミーで取得できます';
const sub = context === 'license'
? '業務委託契約で活動しながら、ソプラアカデミーで動物取扱責任者の要件を満たす資格取得も可能です。'
: 'JPLC認定の通信講座。在宅・最短2ヶ月で取得でき、副業からプロまで対応。';
return (
🎓
SOPRA ACADEMY
{headline}
{sub}
{academy.courses.map(c=>(
{c.badge && (
{c.badge}
)}
{c.name.replace(/ペットシッター/,'').trim() || c.name}
¥{c.price.toLocaleString()}
税込
))}
);
}
function Step3({form, toggle}) {
const types = [
['visit','訪問ケア','飼い主様のお宅にお伺いします',Icons.Pin],
['walk','お散歩のみ','お家までお迎えに行きます',Icons.Sparkle],
['stay','お預かり(自宅)','シッターの自宅でお預かり',Icons.Heart],
['overnight','お泊まり','宿泊を含む長時間のケア',Icons.Calendar],
];
return (<>
{types.map(([k,n,d,I])=>{
const sel = form.serviceTypes.includes(k);
return (
);
})}
>);
}
function Step4({form, set, toggle}) {
const days = [['mon','月'],['tue','火'],['wed','水'],['thu','木'],['fri','金'],['sat','土'],['sun','日']];
const pricing = (typeof window !== 'undefined' && window.PALPET_PRICING) || { base:{ '60':3300 } };
return (<>
{/* 統一料金の説明 */}
料金はパルペットで統一されています
19年のペットケア実績を持つソプラ銀座が運営するため、シッター間で料金を競わせる必要はありません。
人材の質を担保しているからこそ、すべてのシッターに同じ料金が支払われます。
{(pricing.plans?.standard?.tiers || []).map((tier)=>(
{tier.minutes}分
¥{tier.price.toLocaleString()}
))}
※ 税込・1回の訪問あたり(基本ペットシッタープラン)。延長30分毎 ¥3,000。
ロング・深夜・定額プランも別途あります。
※ 別途交通費(同区内 ¥500目安)。シッターへの報酬は別の料金表で公開されています。
{days.map(([k,n])=>{
const sel = form.days.includes(k);
return (
);
})}
>);
}
function Step5({form, set}) {
return (<>
set('profilePhoto', f)}
title="顔がはっきり写った写真を1枚"
hint="笑顔の写真、ペットと一緒の写真はよりおすすめ。サングラスは不可です(5MB以内・JPEG/PNG)。"
sampleTone="warm"/>
>);
}
function Step6({form, set}) {
return (<>
set('idDocument', f)}
title="身分証アップロード"
hint="運転免許証・マイナンバーカードなど(5MB以内)"
accept="image/*,.pdf"/>
set('selfie', f)}
title="セルフィー撮影"
hint="身分証と同じ顔の写真(5MB以内)"
accept="image/*"/>
{/* ペット保険同意 / 利用規約 — チェックボックス系(写真ではない) */}
{[
['ペット保険同意','最大1,000万円の補償', Icons.Shield, 'insAgree'],
['利用規約への同意','プライバシーポリシー含む', Icons.Check, 'tosAgree'],
].map(([t,s,I,k],i)=>{
const done = !!form[k];
return (
);
})}
審査基準について
本人確認・反社チェック・SNS確認・面接(ビデオ通話)を経て、登録の可否をお伝えします。
審査期間は通常 3〜5営業日です。
「送信して審査へ」を押すと、登録内容が運営の管理用スプレッドシートに記録され、Palpet運営にメール通知が届きます。
審査結果のご連絡まで、しばらくお待ちください。
>);
}
// ---------- Right preview ----------
function PreviewCard({form}) {
const petLabel = {dog:'犬',cat:'猫',rabbit:'うさぎ',bird:'鳥',reptile:'爬虫類',fish:'魚'};
return (
みかこ
29歳
{form.idVerified && (
本人確認済
)}
港区南青山 ・ 経験 {form.exp}年
{form.stations && form.stations.length > 0 && (
{form.stations.slice(0,4).map((s,i)=>(
{s}
))}
{form.stations.length > 4 && (
+{form.stations.length-4}
)}
)}
対応:{form.pets.map(p=>petLabel[p]).join('・')}
サービス:{form.serviceTypes.map(s=>({visit:'訪問',walk:'散歩',stay:'お預かり',overnight:'お泊まり'})[s]).join('・')}
{form.skills.slice(0,3).map((k,i)=>(
{({walk:'散歩',med:'投薬',groom:'ブラッシング',behavior:'問題行動',senior:'シニア',kitten:'子犬・子猫'})[k]}
))}
);
}
// ===== 送信状態フィードバック(シッター登録 & 飼い主登録で共用) =====
function SubmitFeedback({ state, onReset }) {
const { phase, message, res } = state;
const palette = {
loading: { bg:'var(--c-primary-soft)', fg:'var(--c-primary-deep)', icon:'⏳' },
success: { bg:'#E8F5E0', fg:'#3a7a2a', icon:'✓' },
error: { bg:'color-mix(in oklab, var(--c-accent) 22%, #fff)',
fg:'#8b6a14', icon:'⚠️' },
}[phase] || { bg:'#fff', fg:'#3c2f1e', icon:'' };
return (
{palette.icon}
{phase==='loading' && '送信中…'}
{phase==='success' && '送信完了'}
{phase==='error' && '送信エラー'}
{message}
{phase==='success' && res?.demo && (
※ デモモード表示(送信は実行されていません)。本番モードで運用されます。
)}
{phase === 'error' && (
)}
);
}
window.SitterRegister = SitterRegister;
window.SubmitFeedback = SubmitFeedback;
window.RegField = Field;
window.regInputCss = inputCss;