ba-cafe

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

Settings.tsx (6937B)


      1 "use client";
      2 
      3 import { useState } from 'react';
      4 import { useAuth, supabase } from "@/hooks/useAuth"; 
      5 import OneSignal from 'react-onesignal';
      6 
      7 const LogoutIcon = ({ className = 'h-5 w-5 mr-2' }: { className?: string }) => (
      8   <svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
      9     <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" />
     10   </svg>
     11 );
     12 const MenuItemIcon = () => (
     13   <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 mr-3 text-gray-500" fill="none" viewBox="0 1 24 24" stroke="currentColor" strokeWidth={2}>
     14     <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
     15   </svg>
     16 );
     17 const TrashIcon = ({ className = 'h-5 w-5 mr-2' }: { className?: string }) => (
     18   <svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
     19     <path strokeLinecap="round" strokeLinejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M9 7V5a2 2 0 012-2h2a2 2 0 012 2v2M7 7h10" />
     20   </svg>
     21 );
     22 const BellIcon = ({ className = 'h-5 w-5 mr-2' }: { className?: string }) => (
     23   <svg xmlns="http://www.w3.org/2000/svg" className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
     24     <path strokeLinecap="round" strokeLinejoin="round" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
     25   </svg>
     26 );
     27 
     28 interface SettingsProps {
     29   onOpenContent: (key: string) => void;
     30 }
     31 
     32 const Settings = ({ onOpenContent }: SettingsProps) => {
     33   const { isLoggedIn, logout, avatarUrl, displayName } = useAuth();
     34   const [isDeleting, setIsDeleting] = useState(false);
     35   const [isPushLoading, setIsPushLoading] = useState(false);
     36 
     37   const menuItems = [
     38     { label: 'About', key: 'about' },
     39     { label: '使い方ガイド', key: 'guide' },
     40     { label: 'FAQ', key: 'faq' },
     41     { label: '利用規約', key: 'terms' },
     42     { label: 'プライバシーポリシー', key: 'privacy' },
     43     { label: '運営者情報など', key: 'operator' },
     44   ];
     45 
     46   const handleNotificationClick = async () => {
     47     if (isPushLoading) return;
     48     setIsPushLoading(true);
     49 
     50     try {
     51       if (typeof window === 'undefined' || !OneSignal.User) return;
     52 
     53       const isAllowed = await OneSignal.Notifications.requestPermission();
     54       if (!isAllowed) {
     55         alert("通知がブロックされています。ブラウザの設定で許可してください。");
     56         return;
     57       }
     58 
     59       const { data: { session } } = await supabase.auth.getSession();
     60       if (session?.user) {
     61         await OneSignal.login(session.user.id);
     62       }
     63 
     64       const isOptedIn = OneSignal.User.PushSubscription.optedIn;
     65       if (isOptedIn) {
     66         await OneSignal.User.PushSubscription.optOut();
     67         alert("この端末の通知をOFFにしました。");
     68       } else {
     69         await OneSignal.User.PushSubscription.optIn();
     70         alert("この端末の通知をONにしました!");
     71       }
     72 
     73     } catch (e) {
     74       console.error("Notification setting error:", e);
     75       alert("設定の切り替えに失敗しました。");
     76     } finally {
     77       setIsPushLoading(false);
     78     }
     79   };
     80 
     81   const handleLogoutClick = async () => {
     82     if (window.confirm("ログアウトしますか?")) {
     83       await logout();
     84       window.location.href = '/';
     85     }
     86   };
     87 
     88   const handleDeleteData = async () => {
     89     const firstConfirm = window.confirm("本当に全データを削除しますか?\nこの操作は取り消せません。");
     90     if (!firstConfirm) return;
     91 
     92     const secondConfirm = window.confirm("本当に、すべての記録を完全に消去してもよろしいですか?\nこの操作は取り消せません。");
     93     if (!secondConfirm) return;
     94 
     95     setIsDeleting(true);
     96     try {
     97       const { error } = await supabase.rpc('delete_user_account');
     98       if (error) throw error;
     99       alert("すべてのデータを削除しました。");
    100       await logout();
    101       window.location.href = "/";
    102     } catch (err: any) {
    103       console.error(err);
    104       alert("削除に失敗しました: " + err.message);
    105     } finally {
    106       setIsDeleting(false);
    107     }
    108   };
    109   
    110   return (
    111     <div className="p-4">
    112       <div className="space-y-4">
    113         <ul className="space-y-1">
    114           {menuItems.map((item) => (
    115             <li key={item.key}>
    116               <button 
    117                 onClick={() => onOpenContent(item.key)}
    118                 className="btn-setting flex items-center w-full p-2 rounded transition-colors"
    119               >
    120                 <MenuItemIcon />
    121                 <span>{item.label}</span>
    122               </button>
    123             </li>
    124           ))}
    125         </ul>
    126 
    127         <div className="mt-8 border-t pt-4">
    128           {isLoggedIn && (
    129             <>
    130               <div className="flex items-center gap-4 mb-4 p-2 rounded-lg">
    131                 {avatarUrl ? (
    132                   <img src={avatarUrl} alt="avatar" className="w-10 h-10 rounded-full"/>
    133                 ) : (
    134                   <div className="w-10 h-10 rounded-full flex items-center justify-center bg-gray-200">
    135                     <span className="text-xl">?</span>
    136                   </div>
    137                 )}
    138                 <div className="flex flex-col">
    139                   <span className="font-semibold">{displayName}</span>
    140                 </div>
    141               </div>
    142 
    143               <div className="grid grid-cols-1 gap-4">
    144                 <button 
    145                   onClick={handleNotificationClick} 
    146                   disabled={isPushLoading}
    147                   className="btn-setting flex items-center justify-center p-2 rounded transition-opacity active:opacity-70 disabled:opacity-50"
    148                 >
    149                   <BellIcon />
    150                   <span>{isPushLoading ? '設定中...' : '通知設定'}</span>
    151                 </button>
    152                 <button 
    153                   onClick={handleDeleteData} 
    154                   disabled={isDeleting} 
    155                   className="btn-setting flex items-center justify-center p-2 rounded transition-opacity active:opacity-70"
    156                 >
    157                   {isDeleting ? (<><TrashIcon /><span>削除中…</span></>) : (<><TrashIcon /><span>データ削除</span></>)}
    158                 </button>
    159                 <button 
    160                   onClick={handleLogoutClick} 
    161                   className="btn-setting flex items-center justify-center p-2 rounded transition-opacity active:opacity-70"
    162                 >
    163                   <LogoutIcon />
    164                   <span>ログアウト</span>
    165                 </button>
    166               </div>
    167             </>
    168           )}
    169         </div>
    170       </div>
    171     </div>
    172   );
    173 };
    174 
    175 export default Settings;