// QReward Customer — Notifications, Profile + subpages const { useState: uS5, useEffect: uE5 } = React; /* ============ NOTIFICATIONS ============ */ const NotificationsScreen = ({ lang, onBack, onNav }) => { const [tab, setTab] = uS5('All'); const [items, setItems] = uS5(DATA.notifications); const tabs = ['All', 'Promotions', 'Orders', 'Account']; const tabsJp = { All: 'すべて', Promotions: 'プロモ', Orders: '注文', Account: 'アカウント' }; const filtered = items.filter(n => tab === 'All' || n.cat === tab); const iconColor = { promo: 'var(--amber-600)', points: 'var(--green-600)', wishlist: 'var(--red-500)', order: 'var(--info-600)', cart: 'var(--brand-600)', legal: 'var(--purple-600)' }; const iconBg = { promo: 'var(--amber-100)', points: 'var(--green-100)', wishlist: 'var(--red-100)', order: 'var(--info-100)', cart: 'var(--brand-100)', legal: 'var(--purple-100)' }; return (
setItems(items.map(n => ({ ...n, unread: false })))}>{lang === 'jp' ? 'すべて既読' : 'Mark all read'}} />
{tabs.map(t => )}
{filtered.length === 0 ?
{lang === 'jp' ? '通知はありません' : 'No notifications'}
:
{filtered.map(n => ( ))}
}
); }; /* ============ PROFILE ============ */ const ProfileScreen = ({ lang, onNav, onSignOut, setLang, cardStatus }) => { const [signOutOpen, setSignOut] = uS5(false); const Row = ({ icon, title, sub, onClick, danger, trailing }) => ( ); return (
{/* User tile */}
{DATA.user.initials}
{lang === 'jp' ? `${DATA.user.nameJp} さん` : 'Giri Tanaka'}
{lang === 'jp' ? '登録日' : 'Joined'} {DATA.user.joined}
{lang === 'jp' ? 'アカウント' : 'Account'}
onNav('push', 'personalInfo')} /> onNav('push', 'linked')} />
{lang === 'jp' ? '設定' : 'Preferences'}
{lang === 'jp' ? '日本語' : 'English'}} onClick={() => onNav('push', 'language')} /> onNav('push', 'notifSettings')} />
{lang === 'jp' ? 'セキュリティ' : 'Security'}
onNav('push', 'changePw')} /> } onClick={() => {}} /> onNav('push', 'changePin')} /> onNav('push', 'sessions')} />
{lang === 'jp' ? '法的事項・アカウント' : 'Legal & Account'}
onNav('push', 'legal', { doc: 'Terms of Service' })} /> onNav('push', 'legal', { doc: 'Privacy Policy' })} /> onNav('push', 'deleteAccount')} />
setSignOut(false)} title={lang === 'jp' ? 'サインアウトしますか?' : 'Sign out of QReward?'}> {lang === 'jp' ? 'サインアウト' : 'Sign out'}
); }; /* ============ PROFILE SUBPAGES ============ */ const SubPage = ({ title, onBack, lang, children, footer }) => (
{children}
{footer &&
{footer}
}
); const PersonalInfoScreen = ({ lang, onBack, toast }) => { const [edited, setEdited] = uS5(false); return ( { toast(lang === 'jp' ? '保存しました' : 'Saved'); setEdited(false); }}>{lang === 'jp' ? '変更を保存' : 'Save changes'}}>
{[ { label: lang === 'jp' ? '氏名' : 'Name', val: 'Giri Tanaka', icon: 'user' }, { label: lang === 'jp' ? 'メール' : 'Email', val: DATA.user.email, icon: 'mail' }, { label: lang === 'jp' ? '電話番号' : 'Phone', val: '+81 90-1234-5678', icon: 'phone' }, ].map((f, i) => (
setEdited(true)} />
))}
{lang === 'jp' ? '保存済みの住所' : 'Saved addresses'}
{DATA.addresses.map(a => (
{a.name}
{a.zip} {a.line}
))}
); }; const LanguageScreen = ({ lang, setLang, onBack, toast }) => (
{[{ id: 'en', label: 'English' }, { id: 'jp', label: '日本語' }].map(o => ( ))}
); const NotifSettingsScreen = ({ lang, onBack }) => { const [master, setMaster] = uS5(true); const cats = [lang === 'jp' ? 'プロモーション' : 'Promotions', lang === 'jp' ? '注文' : 'Orders', lang === 'jp' ? 'アカウント' : 'Account', lang === 'jp' ? 'ウィッシュリスト' : 'Wishlist', lang === 'jp' ? 'カート' : 'Cart']; const [toggles, setToggles] = uS5(cats.map(() => true)); return (
{lang === 'jp' ? 'QReward通知' : 'QReward notifications'}
{master && <>
{lang === 'jp' ? 'カテゴリ' : 'Categories'}
{cats.map((c, i) => (
{c}
))}
}
); }; const ChangePwScreen = ({ lang, onBack, toast }) => { const [old, setOld] = uS5(''); const [nw, setNw] = uS5(''); const [cf, setCf] = uS5(''); const score = [nw.length >= 8, /[A-Z]/.test(nw) && /[a-z]/.test(nw), /\d/.test(nw), /[^A-Za-z0-9]/.test(nw)].filter(Boolean).length; const valid = old.length >= 4 && score >= 3 && nw === cf && nw !== old; return ( { toast(lang === 'jp' ? 'パスワードを変更しました' : 'Password changed'); onBack(); }}>{lang === 'jp' ? '保存' : 'Save'}}>
setOld(e.target.value)} />
setNw(e.target.value)} />
{[1, 2, 3, 4].map(k =>
= k ? 'on ' + ['', 'weak', 'fair', 'good', 'strong'][score] : ''}`}>
)}
setCf(e.target.value)} style={cf && cf !== nw ? { borderColor: 'var(--red-500)' } : null} />
); }; const ChangePinScreen = ({ lang, onBack, toast }) => { const [phase, setPhase] = uS5('current'); // current | new | confirm const [pin, setPin] = uS5([]); const [newPin, setNewPin] = uS5([]); const titles = { current: lang === 'jp' ? '現在のPIN' : 'Enter current PIN', new: lang === 'jp' ? '新しいPIN' : 'Enter new PIN', confirm: lang === 'jp' ? '新しいPINを確認' : 'Confirm new PIN' }; const cur = phase === 'new' ? newPin : pin; const press = (n) => { if (cur.length >= 6) return; const nc = [...cur, n]; if (phase === 'new') setNewPin(nc); else setPin(nc); if (nc.length === 6) setTimeout(() => { if (phase === 'current') { setPin([]); setPhase('new'); } else if (phase === 'new') { setPhase('confirm'); setPin([]); } else { toast(lang === 'jp' ? 'PINを変更しました' : 'PIN changed'); onBack(); } }, 150); }; const del = () => phase === 'new' ? setNewPin(newPin.slice(0, -1)) : setPin(pin.slice(0, -1)); return (
{titles[phase]}
{[0, 1, 2, 3, 4, 5].map(i =>
i ? 'filled' : ''}`}>
)}
); }; const SessionsScreen = ({ lang, onBack, toast }) => { const sessions = [ { device: 'iPhone 15 Pro', loc: 'Tokyo, JP', platform: 'iOS', active: lang === 'jp' ? '現在' : 'Now', current: true }, { device: 'iPad Air', loc: 'Tokyo, JP', platform: 'iOS', active: lang === 'jp' ? '2時間前' : '2h ago' }, { device: 'Chrome · MacBook', loc: 'Osaka, JP', platform: 'Web', active: lang === 'jp' ? '昨日' : 'Yesterday' }, ]; return (
{sessions.map((s, i) => (
{s.device} {s.current && {lang === 'jp' ? 'このデバイス' : 'This device'}}
{s.loc} · {s.platform} · {s.active}
{!s.current && }
))}
toast(lang === 'jp' ? '他のデバイスからサインアウトしました' : 'Signed out other devices')}>{lang === 'jp' ? '他のすべてのデバイスからサインアウト' : 'Sign out all other devices'}
); }; const LegalScreen = ({ lang, onBack, doc }) => (
{lang === 'jp' ? '新しいバージョンがあります — 確認して同意してください。' : 'A new version is available — review and accept.'}
{doc} · v3.2 · 2026/02/14

{lang === 'jp' ? '本規約は、QRewardサービスの利用条件を定めるものです。本サービスを利用することにより、利用者は本規約に同意したものとみなされます。' : 'These terms govern your use of the QReward service. By using the service, you agree to be bound by these terms and our handling of your data as described in the Privacy Policy.'}

{lang === 'jp' ? 'ポイントは提携先からのデータに基づいて付与され、マーケットプレイスでの交換に利用できます。ポイントに現金的価値はありません。' : 'Points are credited based on partner data and may be redeemed in the marketplace. Points hold no cash value and are subject to partner conversion ratios in effect at the time of import.'}

{lang === 'jp' ? '当社は、適用法令に従い、利用者の個人情報を保護します。' : 'We protect your personal information in accordance with applicable law, including Japan\'s Act on Protection of Personal Information.'}

); const DeleteAccountScreen = ({ lang, onBack, onConfirm }) => { const [step, setStep] = uS5(1); const [understand, setUnderstand] = uS5(false); const [reason, setReason] = uS5(''); const [email, setEmail] = uS5(''); const [pw, setPw] = uS5(''); const [finalSheet, setFinalSheet] = uS5(false); const reasons = lang === 'jp' ? ['使わなくなった', 'プライバシーの懸念', '別のアプリを使用', 'その他'] : ['No longer using it', 'Privacy concerns', 'Using another app', 'Other']; return (
step === 1 ? onBack() : setStep(step - 1)} lang={lang} />
= 2 ? 'active' : ''}`}>
= 3 ? 'active' : ''}`}>
{step === 1 && <>
{lang === 'jp' ? '削除する前に' : 'Before you delete'}
{lang === 'jp' ? '2週間以内にポイントを使い切ってください。期限を過ぎるとポイントとアカウントは削除されます。' : 'Your account and remaining points will be deleted after 2 weeks. Use your points before then to keep them.'}
} {step === 2 && <>
{lang === 'jp' ? '理由をお聞かせください' : 'Tell us why'}
{reasons.map(r => )}
} {step === 3 && <>
{lang === 'jp' ? '本人確認' : 'Verify your identity'}
setEmail(e.target.value)} />
setPw(e.target.value)} />
}
{step < 3 ? setStep(step + 1)}>{L(lang, 'continue')} : setFinalSheet(true)}>{lang === 'jp' ? 'アカウントを削除' : 'Delete account'}}
setFinalSheet(false)} title={lang === 'jp' ? 'この操作は取り消せません' : 'This action cannot be undone'}>
{lang === 'jp' ? '本当にアカウントを削除しますか?' : 'Are you sure you want to delete your account?'}
{lang === 'jp' ? '削除する' : 'Delete account'}
); }; Object.assign(window, { NotificationsScreen, ProfileScreen, PersonalInfoScreen, LanguageScreen, NotifSettingsScreen, ChangePwScreen, ChangePinScreen, SessionsScreen, LegalScreen, DeleteAccountScreen, });