NewFeatureDialog.tsx (4379B)
1 "use client"; 2 3 import { useState, useEffect } from 'react'; 4 import { UPDATE_LOGS } from '@/lib/updateLogs'; 5 6 export default function NewFeatureDialog() { 7 const [isOpen, setIsOpen] = useState(false); 8 const [isChecked, setIsChecked] = useState(false); 9 10 const latestLog = UPDATE_LOGS[0]; 11 const pastLogs = UPDATE_LOGS.slice(1); 12 13 const latestLogId = latestLog ? `${latestLog.date}_${latestLog.content}` : 'initial'; 14 15 useEffect(() => { 16 if (typeof window !== 'undefined') { 17 const lastNotifiedId = localStorage.getItem('new_feature_notified_id'); 18 if (lastNotifiedId !== latestLogId) { 19 setIsOpen(true); 20 } 21 } 22 }, [latestLogId]); 23 24 const handleClose = () => { 25 if (isChecked) { 26 localStorage.setItem('new_feature_notified_id', latestLogId); 27 } 28 setIsOpen(false); 29 }; 30 31 if (!isOpen) return null; 32 33 return ( 34 <div className="fixed inset-0 bg-black/40 dark:bg-black/60 z-100 flex justify-center items-center p-4 animate-in fade-in duration-200 backdrop-blur-sm"> 35 <div className="w-full max-w-sm rounded-2xl shadow-xl border border-dashed bg-(--card) border-(--muted) flex flex-col overflow-hidden animate-in zoom-in-95 duration-200"> 36 37 <div className="border-b border-dashed flex justify-between items-center p-5"> 38 <h2 className="text-xl font-bold text-(--foreground) tracking-wide flex items-center gap-2"> 39 アップデートのお知らせ 40 </h2> 41 </div> 42 43 <div className=" flex flex-col text-sm font-medium leading-relaxed text-(--secondary-foreground)"> 44 45 {/* 1. 最新の更新履歴とコメント(上部に大きく配置) */} 46 {latestLog && ( 47 <div className="bg-(--background) p-4 border border-dashed border-(--primary)/30 flex flex-col gap-2"> 48 <div className="flex items-center gap-2 text-s font-bold text-(--secondary-foreground)"> 49 <span>{latestLog.date}</span> 50 </div> 51 <strong className="text-base font-bold text-(--foreground) block"> 52 {latestLog.content} 53 </strong> 54 {latestLog.comment && ( 55 <p className="text-sm text-(--secondary-foreground) font-medium"> 56 {latestLog.comment} 57 </p> 58 )} 59 </div> 60 )} 61 62 {/* 2. それ以前の更新履歴(下部にリスト表示) */} 63 {pastLogs.length > 0 && ( 64 <div className="flex flex-col gap-2"> 65 <div className="show-scrollbar bg-(--background) p-4 border border-dashed border-(--muted)/60 flex flex-col gap-2.5 max-h-36 overflow-y-auto"> 66 {pastLogs.map((log, index) => ( 67 <div key={index} className="flex flex-col gap-0.5 text-xs"> 68 <div className="flex gap-3"> 69 <span className="shrink-0 font-bold text-(--muted-foreground)">{log.date}</span> 70 <p className="text-(--secondary-foreground)">{log.content}</p> 71 </div> 72 {log.comment && ( 73 <p className="text-[11px] text-(--secondary-foreground)/70 pl-18.5 leading-normal"> 74 {log.comment} 75 </p> 76 )} 77 </div> 78 ))} 79 </div> 80 </div> 81 )} 82 </div> 83 84 <div className="p-4 bg-(--background)/50 border-t border-dashed border-(--muted)/40 flex flex-col gap-3"> 85 <label className="flex items-center gap-2 px-1 cursor-pointer select-none text-xs text-(--secondary-foreground) font-bold"> 86 <input 87 type="checkbox" 88 checked={isChecked} 89 onChange={(e) => setIsChecked(e.target.checked)} 90 className="w-4 h-4 rounded border-(--muted) text-(--primary) focus:ring-(--primary) cursor-pointer" 91 /> 92 <span>今回のアップデートノートを今後表示しない</span> 93 </label> 94 95 <button 96 onClick={handleClose} 97 className="w-full py-3 rounded-xl font-bold bg-(--primary) text-(--primary-foreground) transition-all active:scale-98 cursor-pointer shadow-sm hover:brightness-105" 98 > 99 OK 100 </button> 101 </div> 102 103 </div> 104 </div> 105 ); 106 }