11import type { Dispatch , SetStateAction } from "react" ;
22import dynamic from "next/dynamic" ;
3+ import { useEffect , useState } from "react" ;
4+ import { useSession } from "next-auth/react" ;
5+ import Swal from "sweetalert2" ;
36
47// Components
58import MetaDataForm from "./MetaDataForm" ;
@@ -17,11 +20,13 @@ interface Props {
1720 postFunction : Function ;
1821 postButtonDisable : boolean ;
1922 isEdit ?: boolean ;
23+ handleFileChange : Function ;
2024}
2125
2226const Editor = dynamic ( ( ) => import ( "./Editor" ) , {
2327 ssr : false ,
2428} ) ;
29+
2530export default function NewPostMobile ( {
2631 setPostData,
2732 data,
@@ -31,20 +36,230 @@ export default function NewPostMobile({
3136 postFunction,
3237 postButtonDisable,
3338 isEdit,
39+ handleFileChange,
3440} : Props ) {
41+ const { status } = useSession ( ) ;
42+ const [ tagInput , setTagInput ] = useState ( "" ) ;
43+ const [ tags , setTags ] = useState < string [ ] > ( [ ] ) ;
44+
45+ // 從 data 初始化 tags
46+ useEffect ( ( ) => {
47+ if ( data ?. hashtag ) {
48+ setTags ( Array . isArray ( data . hashtag ) ? data . hashtag : [ ] ) ;
49+ }
50+ } , [ data ?. hashtag ] ) ;
51+
52+ const showLoginAlert = ( ) => {
53+ Swal . fire ( {
54+ icon : "warning" ,
55+ title : "需要登入" ,
56+ text : "請先登入才能上傳封面圖片" ,
57+ confirmButtonText : "確定" ,
58+ confirmButtonColor : "#5FCDF5" ,
59+ } ) ;
60+ } ;
61+
62+ const handleInputChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
63+ // 檢查登入狀態
64+ if ( status !== "authenticated" ) {
65+ showLoginAlert ( ) ;
66+ e . target . value = "" ; // 清空 input
67+ return ;
68+ }
69+
70+ console . log ( "File input changed:" , e . target . files ) ;
71+ if ( handleFileChange ) {
72+ handleFileChange ( e ) ;
73+ }
74+ } ;
75+
76+ const handleFileInputClick = ( e : React . MouseEvent < HTMLLabelElement > ) => {
77+ // 檢查登入狀態
78+ if ( status !== "authenticated" ) {
79+ e . preventDefault ( ) ;
80+ showLoginAlert ( ) ;
81+ return ;
82+ }
83+ } ;
84+
85+ const handleRemoveCover = ( e : React . MouseEvent ) => {
86+ e . preventDefault ( ) ;
87+ e . stopPropagation ( ) ;
88+
89+ if ( handleFormEventFunction ) {
90+ handleFormEventFunction ( { target : { name : "cover" , value : "" } } ) ;
91+ }
92+
93+ const fileInput = document . getElementById ( "file" ) as HTMLInputElement ;
94+ if ( fileInput ) {
95+ fileInput . value = "" ;
96+ }
97+ } ;
98+
99+ const handleAddTag = ( ) => {
100+ const trimmedTag = tagInput . trim ( ) ;
101+ if ( trimmedTag && ! tags . includes ( trimmedTag ) ) {
102+ const newTags = [ ...tags , trimmedTag ] ;
103+ setTags ( newTags ) ;
104+ setTagInput ( "" ) ;
105+
106+ // 更新父組件的 data
107+ if ( handleFormEventFunction ) {
108+ handleFormEventFunction ( {
109+ target : { name : "hashtag" , value : newTags } ,
110+ } ) ;
111+ }
112+ }
113+ } ;
114+
115+ const handleRemoveTag = ( tagToRemove : string ) => {
116+ const newTags = tags . filter ( ( tag ) => tag !== tagToRemove ) ;
117+ setTags ( newTags ) ;
118+
119+ // 更新父組件的 data
120+ if ( handleFormEventFunction ) {
121+ handleFormEventFunction ( { target : { name : "hashtag" , value : newTags } } ) ;
122+ }
123+ } ;
124+
125+ const handleTagInputKeyPress = ( e : React . KeyboardEvent < HTMLInputElement > ) => {
126+ if ( e . key === "Enter" ) {
127+ e . preventDefault ( ) ;
128+ handleAddTag ( ) ;
129+ }
130+ } ;
131+
35132 return (
36- < div className = "pt-[4.5rem] px-2 pb-[4.5rem] h-[100dvh]" >
37- < div className = "bg-white h-full rounded-lg p-4 pr-2 overflow-hidden" >
38- < div className = "flex flex-col items-center overflow-y-auto small-scrollbar h-full pr-2" >
39- < MetaDataForm
40- data = { data }
41- handleFormEventFunction = { handleFormEventFunction }
42- />
43- < h2 className = "text-left w-full h-auto mt-4 mb-1 text-lg" > Content</ h2 >
44- < hr className = "border-gray-300 h-1 w-full" />
45- < div className = "h-auto w-full mb-2 mt-2" >
133+ < div className = "pt-[4.5rem] px-2 pb-[4.5rem] h-[100dvh] overflow-x-hidden" >
134+ < div className = "bg-white h-full rounded-lg p-4 overflow-hidden" >
135+ < div className = "flex flex-col items-center overflow-y-auto small-scrollbar h-full pr-2 overflow-x-hidden" >
136+ < div className = "w-full max-w-full" >
137+ < MetaDataForm
138+ data = { data }
139+ handleFormEventFunction = { handleFormEventFunction }
140+ />
141+ </ div >
142+
143+ { /* Content Section */ }
144+ < h2 className = "text-left w-full h-auto mt-6 mb-2 text-lg font-semibold text-gray-700" >
145+ Content
146+ </ h2 >
147+ < hr className = "border-gray-200 h-px w-full mb-3" />
148+ < div className = "h-auto w-full max-w-full mb-4" >
46149 < Editor setPostData = { setPostData } data = { data } token = { token } />
47150 </ div >
151+
152+ { /* Hashtag Section */ }
153+ < h2 className = "text-left w-full h-auto mt-4 mb-2 text-lg font-semibold text-gray-700" >
154+ Hashtag
155+ </ h2 >
156+ < hr className = "border-gray-200 h-px w-full mb-3" />
157+ < div className = "w-full max-w-full" >
158+ { /* Tag Input */ }
159+ < div className = "flex gap-2 mb-3 h-auto w-full" >
160+ < input
161+ type = "text"
162+ value = { tagInput }
163+ onChange = { ( e ) => setTagInput ( e . target . value ) }
164+ onKeyDown = { handleTagInputKeyPress }
165+ placeholder = "新增標籤..."
166+ className = "flex-1 px-4 py-2.5 border border-gray-300 rounded-lg text-sm w-full"
167+ />
168+ < button
169+ onClick = { handleAddTag }
170+ className = "px-5 py-2.5 bg-[#5FCDF5] text-white rounded-lg transition-colors font-medium text-sm whitespace-nowrap flex-none"
171+ >
172+ ADD
173+ </ button >
174+ </ div >
175+
176+ { /* Tags Display */ }
177+ { tags . length > 0 && (
178+ < div className = "flex flex-wrap gap-2 p-3 bg-gray-50 rounded-lg max-w-full" >
179+ { tags . map ( ( tag ) => (
180+ < span
181+ key = { tag }
182+ className = "inline-flex items-center gap-1.5 bg-blue-100 text-blue-600 px-3 py-1.5 rounded-full text-sm font-medium break-all"
183+ >
184+ #{ tag }
185+ < button
186+ onClick = { ( ) => handleRemoveTag ( tag ) }
187+ className = "active:bg-blue-200 rounded-full w-5 h-5 flex items-center justify-center transition-colors active:scale-90 flex-shrink-0"
188+ >
189+ ×
190+ </ button >
191+ </ span >
192+ ) ) }
193+ </ div >
194+ ) }
195+ </ div >
196+
197+ { /* Cover Section */ }
198+ < h2 className = "text-left w-full h-auto mt-4 mb-2 text-lg font-semibold text-gray-700" >
199+ Cover
200+ </ h2 >
201+ < hr className = "border-gray-200 h-px w-full mb-3" />
202+ < div className = "w-full max-w-full" >
203+ { data ?. cover ? (
204+ < div className = "relative max-w-full" >
205+ < label
206+ htmlFor = "file"
207+ onClick = { handleFileInputClick }
208+ className = "block cursor-pointer active:opacity-90 transition-opacity"
209+ >
210+ < img
211+ src = { data . cover }
212+ alt = "Cover"
213+ className = "w-full max-w-full max-h-64 object-cover rounded-lg"
214+ />
215+ </ label >
216+
217+ < button
218+ onClick = { handleRemoveCover }
219+ className = "absolute top-2 right-2 w-9 h-9 bg-red-500 active:bg-red-600 text-white rounded-full flex items-center justify-center shadow-lg transition-all duration-200 active:scale-90"
220+ title = "移除封面"
221+ >
222+ < svg
223+ xmlns = "http://www.w3.org/2000/svg"
224+ className = "h-5 w-5"
225+ viewBox = "0 0 20 20"
226+ fill = "currentColor"
227+ >
228+ < path
229+ fillRule = "evenodd"
230+ d = "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
231+ clipRule = "evenodd"
232+ />
233+ </ svg >
234+ </ button >
235+ </ div >
236+ ) : (
237+ < label
238+ htmlFor = "file"
239+ onClick = { handleFileInputClick }
240+ className = "min-h-[12rem] border-2 border-dashed border-[#5FCDF5] bg-blue-50 rounded-lg cursor-pointer transition-all active:bg-blue-100 active:scale-[0.98] flex flex-col justify-center items-center gap-3 p-6"
241+ >
242+ < div className = "text-6xl opacity-60" > 📷</ div >
243+ < div className = "text-[#5FCDF5] font-medium text-base text-center" >
244+ 點擊上傳圖片
245+ </ div >
246+ < div className = "text-gray-500 text-sm text-center" >
247+ 支援 JPG、PNG、GIF 格式
248+ </ div >
249+ </ label >
250+ ) }
251+
252+ < input
253+ id = "file"
254+ type = "file"
255+ accept = "image/*"
256+ onChange = { handleInputChange }
257+ className = "hidden"
258+ />
259+ </ div >
260+
261+ { /* Bottom Spacing and Buttons */ }
262+ < div className = "w-full border-t border-gray-200 mt-6 mb-4" > </ div >
48263 < Button
49264 discardFunction = { discardFunction }
50265 postFunction = { postFunction }
@@ -55,4 +270,4 @@ export default function NewPostMobile({
55270 </ div >
56271 </ div >
57272 ) ;
58- }
273+ }
0 commit comments