ba-cafe

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit edaf41b4ab2e2464dc31cb89e3b9688f1c6da7c4
parent ebff170695e61f0a70335e09dfa720178cad92a5
Author: Sunny <122193933+Sunny-JP@users.noreply.github.com>
Date:   Sat, 24 Jan 2026 23:27:16 +0900

Merge pull request #22 from Sunny-JP/v4-sunny-dev

fix api
Diffstat:
Msrc/app/api/tap/route.ts | 17+++++++----------
Msrc/components/Settings.tsx | 235+++++++++++++++++++++++++++++++++++++++++--------------------------------------
2 files changed, 128 insertions(+), 124 deletions(-)

diff --git a/src/app/api/tap/route.ts b/src/app/api/tap/route.ts @@ -62,23 +62,20 @@ async function cleanupDevices(userId: string, currentId: string) { const data = await res.json(); const subs = data.subscriptions || (data.identity && data.identity.subscriptions) || []; - const allPushSubs = subs.filter((s: any) => { - return String(s.type).includes("Push") || String(s.type) === "1"; - }); - - const otherDevices = allPushSubs.filter((s: any) => s.id !== currentId); + const others = subs.filter((s: any) => + (String(s.type).includes("Push") || String(s.type) === "1") && s.id !== currentId + ); - otherDevices.sort((a: any, b: any) => { + others.sort((a: any, b: any) => { const timeA = Number(a.last_active || a.created_at || 0); const timeB = Number(b.last_active || b.created_at || 0); return timeB - timeA; }); - if (allPushSubs.length > 2) { - const toDelete = otherDevices.slice(1); - + const toDelete = others.slice(1); + + if (toDelete.length > 0) { for (const sub of toDelete) { - console.log(`[Cleanup] Removing old subscription to make room: ${sub.id}`); await fetch(`https://onesignal.com/api/v1/apps/${APP_ID}/subscriptions/${sub.id}`, { method: "DELETE", headers: { "Authorization": `Basic ${API_KEY}` } diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import { useAuth, supabase } from "@/hooks/useAuth"; import OneSignal from 'react-onesignal'; +// --- Icons --- const LogoutIcon = ({ className = 'h-5 w-5 mr-2' }: { className?: string }) => ( <svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> <path strokeLinecap="round" strokeLinejoin="round" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> @@ -26,9 +27,7 @@ const BellIcon = ({ className = 'h-5 w-5 mr-2' }: { className?: string }) => ( </svg> ); -interface SettingsProps {} - -const Settings = ({}: SettingsProps) => { +const Settings = () => { const { isLoggedIn, logout, avatarUrl, displayName } = useAuth(); const [isDeleting, setIsDeleting] = useState(false); const [hue, setHue] = useState(120); @@ -48,7 +47,7 @@ const Settings = ({}: SettingsProps) => { document.documentElement.style.setProperty('--tap-h', val.toString()); localStorage.setItem('theme-hue', val.toString()); }; - + const menuItems = [ { label: 'About', path: '/about' }, { label: '使い方ガイド', path: '/guide' }, @@ -57,43 +56,42 @@ const Settings = ({}: SettingsProps) => { { label: '運営者情報', path: '/operator' }, ]; -const handleNotificationClick = async () => { + const handleNotificationClick = async () => { try { if (!OneSignal.User) { - alert("通知システムが読み込まれていません。\n\n「広告ブロッカー」や「コンテンツブロッカー」を使用している場合は、このサイトだけOFFにしてリロードしてください。"); + alert("通知システムが読み込まれていません。\n広告ブロッカーをOFFにしてリロードしてください。"); return; } - const permission = Notification.permission; - - if (permission === 'denied') { - alert("【通知がブロックされています】\n\nブラウザの設定で通知が「ブロック」になっています。\nこのサイトの「通知」を許可(またはリセット)してください。"); + if (Notification.permission === 'denied') { + alert("通知がブロックされています。ブラウザの設定で許可してください。"); return; } - const isOptedIn = OneSignal.User.PushSubscription.optedIn; - const currentSubscriptionId = OneSignal.User.PushSubscription.id; // 現在のデバイスID取得 + const isOptedIn = OneSignal.User.PushSubscription.optedIn; - if (isOptedIn) { - await OneSignal.User.PushSubscription.optOut(); - alert("通知をOFFにしました。"); - } else { - await OneSignal.Notifications.requestPermission(); - await OneSignal.User.PushSubscription.optIn(); - alert("通知をONにしました!"); - } + if (isOptedIn) { + await OneSignal.User.PushSubscription.optOut(); + alert("通知をOFFにしました。"); + } else { + await OneSignal.Notifications.requestPermission(); + await OneSignal.User.PushSubscription.optIn(); + alert("通知をONにしました!"); + } - // 通知ボタンを押したタイミングで API を叩き、整理を実行させる - await fetch('/api/tap', { // 実際のパスに合わせてください - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}` - }, - body: JSON.stringify({ - onesignalId: currentSubscriptionId // これをトリガーにする - }) - }); + await new Promise(r => setTimeout(r, 1500)); + const currentSubscriptionId = OneSignal.User.PushSubscription.id; + + await fetch('/api/tap', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${(await supabase.auth.getSession()).data.session?.access_token}` + }, + body: JSON.stringify({ + onesignalId: currentSubscriptionId + }) + }); } catch (e: any) { console.error("Notification Setup Error:", e); @@ -102,15 +100,14 @@ const handleNotificationClick = async () => { }; const handleLogoutClick = async () => { - if (window.confirm("ログアウトしますか?\n(データは削除されません)")) { + if (window.confirm("ログアウトしますか?")) { await logout(); window.location.href = '/'; } }; const handleDeleteData = async () => { - if (!window.confirm("本当に全データを削除しますか?\n(この操作は取り消せません)")) return; - + if (!window.confirm("本当に全データを削除しますか?")) return; setIsDeleting(true); try { const { data: { user } } = await supabase.auth.getUser(); @@ -129,91 +126,101 @@ const handleNotificationClick = async () => { }; return ( - <div className="p-4"> - <div className="space-y-4"> - <ul className="space-y-1"> - {menuItems.map(item => ( - <li key={item.label}> - <Link href={item.path} className="btn-setting flex items-center p-2 rounded"> - <MenuItemIcon /> - <span>{item.label}</span> - </Link> - </li> - ))} - </ul> - <div className="p-2"> - <div className="flex justify-between items-center mb-1"> - <label className="text-xs font-mono opacity-60">Calendar Color</label> - <span className="text-xs font-mono opacity-60">Hue: {hue}</span> - </div> - <input - type="range" - min="0" - max="360" - value={hue} - onChange={handleHueChange} - className="w-full h-2 rounded-lg appearance-none cursor-pointer" - style={{ - background: `linear-gradient(to right, - hsl(0, 80%, 50%), hsl(60, 80%, 50%), hsl(120, 80%, 50%), - hsl(180, 80%, 50%), hsl(240, 80%, 50%), hsl(300, 80%, 50%), hsl(360, 80%, 50%))` - }} - /> - </div> - <div className="mt-8 border-t pt-4"> - {isLoggedIn && ( - <> - <div className="flex items-center gap-4 mb-4 p-2"> - {avatarUrl ? ( - <img src={avatarUrl} alt="user avatar" className="w-10 h-10 rounded-full" /> - ) : ( - <div className="w-10 h-10 rounded-full bg-gray-300 flex items-center justify-center"> - <span className="text-xl">?</span> - </div> - )} - <div className="flex flex-col"> - <span className="font-semibold">{displayName}</span> - </div> - </div> - <div className="grid grid-cols-1 sm:grid-cols-3 gap-2"> - <button - onClick={handleNotificationClick} - className="cal-nav flex items-center justify-center p-2 rounded w-full" + <div className="p-4"> + <div className="space-y-4"> + {/* Menu List */} + <ul className="space-y-1"> + {menuItems.map((item, index) => ( + <li key={item.label}> + <Link + href={item.path} + className="btn-setting flex items-center p-2 rounded transition-colors" > - <BellIcon /> - <span>通知設定</span> - </button> - - <button - onClick={handleDeleteData} - disabled={isDeleting} - className="cal-nav flex items-center justify-center p-2 rounded" - > - {isDeleting ? ( - <> - <TrashIcon className="h-5 w-5 mr-2 opacity-50" /> - <span>削除中…</span> - </> + <MenuItemIcon /> + <span>{item.label}</span> + </Link> + </li> + ))} + </ul> + + {/* Color Theme Picker */} + <div className="p-3 rounded-xl"> + <div className="flex justify-between items-center mb-2"> + <label className="text-xs font-semibold opacity-70">Theme Color</label> + <span className="text-xs font-mono opacity-60">Hue: {hue}</span> + </div> + <input + type="range" + min="0" + max="360" + value={hue} + onChange={handleHueChange} + className="w-full h-2 rounded-lg appearance-none cursor-pointer" + style={{ + background: `linear-gradient(to right, + hsl(0, 80%, 50%), hsl(60, 80%, 50%), hsl(120, 80%, 50%), + hsl(180, 80%, 50%), hsl(240, 80%, 50%), hsl(300, 80%, 50%), hsl(360, 80%, 50%))` + }} + /> + </div> + + {/* Account Section */} + <div className="mt-8 border-t pt-4"> + {isLoggedIn && ( + <> + <div className="flex items-center gap-4 mb-4 p-2 rounded-lg"> + {avatarUrl ? ( + <img src={avatarUrl} alt="avatar" className="w-10 h-10 rounded-full"/> ) : ( - <> - <TrashIcon /> - <span>データ削除</span> - </> + <div className="w-10 h-10 rounded-full flex items-center justify-center"> + <span className="text-xl">?</span> + </div> )} - </button> - <button - onClick={handleLogoutClick} - className="cal-nav flex items-center justify-center p-2 rounded" - > - <LogoutIcon /> - <span>ログアウト</span> - </button> - </div> - </> - )} + <div className="flex flex-col"> + <span className="font-semibold">{displayName}</span> + </div> + </div> + + <div className="grid grid-cols-1 sm:grid-cols-3 gap-2"> + <button + onClick={handleNotificationClick} + className="cal-nav flex items-center justify-center p-2 rounded transition-opacity active:opacity-70" + > + <BellIcon /> + <span>通知設定</span> + </button> + + <button + onClick={handleDeleteData} + disabled={isDeleting} + className="cal-nav flex items-center justify-center p-2 rounded transition-opacity active:opacity-70" + > + {isDeleting ? ( + <> + <TrashIcon className="mr-2 opacity-50" /> + <span>削除中…</span> + </> + ) : ( + <> + <TrashIcon /> + <span>データ削除</span> + </> + )} + </button> + + <button + onClick={handleLogoutClick} + className="cal-nav flex items-center justify-center p-2 rounded transition-opacity active:opacity-70" + > + <LogoutIcon /> + <span>ログアウト</span> + </button> + </div> + </> + )} + </div> </div> </div> - </div> ); };