22
33import React , { useState , useEffect } from "react" ;
44import { createPortal } from "react-dom" ;
5- import { X , Edit } from "lucide-react" ;
5+ import { X , Edit , ChevronLeft } from "lucide-react" ;
66import { AnnouncementTab } from "./AnnouncementTab" ;
77import { AnnouncementContent } from "./AnnouncementContent" ;
88import { AnnouncementEditor } from "./AnnouncementEditor" ;
99import { announcementTabs } from "./data" ;
1010import { AnnouncementCategory , AnnouncementPage } from "./types" ;
1111import { createClientClientComponent } from "@/utils/supabase/client" ;
1212
13-
1413interface AnnouncementModalProps {
1514 isOpen : boolean ;
1615 onClose : ( ) => void ;
@@ -26,6 +25,7 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
2625 const [ isEditorOpen , setIsEditorOpen ] = useState ( false ) ;
2726 const [ loading , setLoading ] = useState ( true ) ;
2827 const [ error , setError ] = useState < string | null > ( null ) ;
28+ const [ showSidebar , setShowSidebar ] = useState ( true ) ;
2929 const supabase = createClientClientComponent ( ) ;
3030
3131 useEffect ( ( ) => {
@@ -34,22 +34,21 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
3434
3535 // Fetch announcements from database
3636 useEffect ( ( ) => {
37- const fetchAnnouncements = async ( ) => {
38- if ( ! isOpen ) return ;
39- if ( ! supabase ) throw new Error ( ' Supabase client not initialized' ) ;
40-
41- try {
42- setLoading ( true ) ;
43- setError ( null ) ;
44-
45- const { data, error : fetchError } = await supabase
37+ const fetchAnnouncements = async ( ) => {
38+ if ( ! isOpen ) return ;
39+ if ( ! supabase ) throw new Error ( " Supabase client not initialized" ) ;
40+
41+ try {
42+ setLoading ( true ) ;
43+ setError ( null ) ;
44+
45+ const { data, error : fetchError } = await supabase
4646 . from ( "announcements" )
4747 . select ( "*" )
4848 . order ( "category" ) ;
4949
5050 if ( fetchError ) throw fetchError ;
5151
52- // Map database fields to AnnouncementPage type
5352 const mappedData : AnnouncementPage [ ] = ( data || [ ] ) . map ( ( item ) => ( {
5453 category : item . category as AnnouncementCategory ,
5554 title : item . title ,
@@ -59,30 +58,39 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
5958 } ) ) ;
6059
6160 setPages ( mappedData ) ;
62- } catch ( err ) {
63- console . error ( "Error fetching announcements:" , err ) ;
64- setError ( `Failed to load announcements: ${ err instanceof Error ? err . message : 'Unknown error' } ` ) ;
65- } finally {
66- setLoading ( false ) ;
67- }
61+ } catch ( err ) {
62+ console . error ( "Error fetching announcements:" , err ) ;
63+ setError (
64+ `Failed to load announcements: ${ err instanceof Error ? err . message : "Unknown error" } `
65+ ) ;
66+ } finally {
67+ setLoading ( false ) ;
68+ }
6869 } ;
6970
7071 fetchAnnouncements ( ) ;
72+ } , [ isOpen , supabase ] ) ;
73+
74+ // Reset sidebar visibility when modal opens/closes
75+ useEffect ( ( ) => {
76+ if ( isOpen ) {
77+ // On mobile, start with sidebar visible
78+ const isMobile = window . innerWidth < 768 ;
79+ setShowSidebar ( isMobile ? true : true ) ;
80+ }
7181 } , [ isOpen ] ) ;
7282
7383 // Get content for active tab
7484 const activeContent = pages . find ( ( page ) => page . category === activeTab ) ;
75- if ( ! supabase ) throw new Error ( ' Supabase client not initialized' ) ;
85+ if ( ! supabase ) throw new Error ( " Supabase client not initialized" ) ;
7686
7787 const handleSave = async ( updatedPage : AnnouncementPage ) => {
7888 try {
79-
80- // Optimistic update - update UI immediately
81- setPages ( ( prevPages ) =>
82- prevPages . map ( ( page ) =>
83- page . category === updatedPage . category ? updatedPage : page
84- )
85- ) ;
89+ setPages ( ( prevPages ) =>
90+ prevPages . map ( ( page ) =>
91+ page . category === updatedPage . category ? updatedPage : page
92+ )
93+ ) ;
8694
8795 // Upsert to database (insert or update)
8896 const { data, error : upsertError } = await supabase
@@ -96,7 +104,7 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
96104 updated_at : new Date ( ) . toISOString ( ) ,
97105 } ,
98106 {
99- onConflict : "category" , // Use category as unique constraint
107+ onConflict : "category" ,
100108 }
101109 )
102110 . select ( )
@@ -106,7 +114,6 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
106114
107115 console . log ( "Successfully saved announcement:" , data ) ;
108116
109- // Update state with the returned data to ensure sync
110117 setPages ( ( prevPages ) =>
111118 prevPages . map ( ( page ) =>
112119 page . category === updatedPage . category
@@ -123,9 +130,14 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
123130 } catch ( err ) {
124131 console . error ( "Error saving announcement:" , err ) ;
125132 setError ( "Failed to save announcement" ) ;
126-
127- // Optionally: Revert optimistic update
128- // You could refetch the data here to ensure consistency
133+ }
134+ } ;
135+
136+ const handleTabClick = ( category : AnnouncementCategory ) => {
137+ setActiveTab ( category ) ;
138+ // On mobile, hide sidebar after selecting a tab
139+ if ( window . innerWidth < 768 ) {
140+ setShowSidebar ( false ) ;
129141 }
130142 } ;
131143
@@ -142,88 +154,107 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
142154
143155 { /* Modal */ }
144156 < div
145- className = "fixed inset-0 z-[100] flex items-center justify-center p-4 pointer-events-none"
157+ className = "fixed inset-0 z-[100] flex items-center justify-center p-2 sm:p- 4 pointer-events-none"
146158 role = "dialog"
147159 aria-modal = "true"
148160 aria-labelledby = "announcement-modal-title"
149161 >
150- < div className = "flex h-[850px] max-h-[95vh] w-full max-w-screen-2xl overflow-hidden rounded-xl bg-white shadow-2xl dark:bg-gray-900 pointer-events-auto" >
162+ < div className = "flex h-full sm:h- [850px] sm: max-h-[95vh] w-full max-w-screen-2xl overflow-hidden rounded-none sm: rounded-xl bg-white shadow-2xl dark:bg-gray-900 pointer-events-auto" >
151163 { /* Left Sidebar - Tabs */ }
152- < div className = "w-80 border-r border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-950" >
164+ < div
165+ className = { `
166+ ${ showSidebar ? "translate-x-0" : "-translate-x-full" }
167+ md:translate-x-0
168+ fixed md:relative inset-y-0 left-0 z-10
169+ w-full sm:w-80 md:w-80
170+ border-r border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-950
171+ transition-transform duration-300 ease-in-out
172+ ` }
173+ >
153174 { /* Header */ }
154- < div className = "border-b border-gray-200 p-8 dark:border-gray-800" >
175+ < div className = "border-b border-gray-200 p-4 sm:p-6 md:p- 8 dark:border-gray-800" >
155176 < h2
156177 id = "announcement-modal-title"
157- className = "text-2xl font-bold text-gray-900 dark:text-gray-100"
178+ className = "text-xl sm:text- 2xl font-bold text-gray-900 dark:text-gray-100"
158179 >
159180 Codebility
160181 </ h2 >
161- < p className = "mt-2 text-base text-gray-600 dark:text-gray-400" >
182+ < p className = "mt-1 sm:mt-2 text-sm sm: text-base text-gray-600 dark:text-gray-400" >
162183 News & Updates
163184 </ p >
164185 </ div >
165186
166187 { /* Tabs */ }
167- < div className = "space-y-2 p-6" role = "tablist" >
188+ < div className = "space-y-2 p-4 sm:p- 6" role = "tablist" >
168189 { announcementTabs . map ( ( tab ) => (
169190 < AnnouncementTab
170191 key = { tab . id }
171192 id = { tab . id }
172193 label = { tab . label }
173194 isActive = { activeTab === tab . id }
174- onClick = { setActiveTab }
195+ onClick = { handleTabClick }
175196 />
176197 ) ) }
177198 </ div >
178199 </ div >
179200
180201 { /* Right Content Area */ }
181- < div className = "flex flex-1 flex-col" >
202+ < div className = "flex flex-1 flex-col min-w-0 " >
182203 { /* Header with Close and Edit Buttons */ }
183- < div className = "flex items-center justify-between border-b border-gray-200 p-8 dark:border-gray-800" >
184- < h3 className = "text-2xl font-semibold text-gray-900 dark:text-gray-100" >
204+ < div className = "flex items-center justify-between border-b border-gray-200 p-4 sm:p-6 md:p-8 dark:border-gray-800 gap-2" >
205+ { /* Back button for mobile */ }
206+ < button
207+ onClick = { ( ) => setShowSidebar ( true ) }
208+ className = "md:hidden rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300 flex-shrink-0"
209+ aria-label = "Back to categories"
210+ >
211+ < ChevronLeft className = "h-5 w-5" />
212+ </ button >
213+
214+ < h3 className = "text-lg sm:text-xl md:text-2xl font-semibold text-gray-900 dark:text-gray-100 truncate flex-1 min-w-0" >
185215 { announcementTabs . find ( ( tab ) => tab . id === activeTab ) ?. label }
186216 </ h3 >
187- < div className = "flex items-center gap-2" >
217+
218+ < div className = "flex items-center gap-1 sm:gap-2 flex-shrink-0" >
188219 < button
189220 onClick = { ( ) => setIsEditorOpen ( true ) }
190221 disabled = { loading }
191- className = "flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950 disabled:opacity-50 disabled:cursor-not-allowed"
222+ className = "flex items-center gap-1 sm:gap- 2 rounded-lg px-2 sm:px-3 md:px- 4 py-2 text-xs sm: text-sm font-medium text-blue-600 transition-colors hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-950 disabled:opacity-50 disabled:cursor-not-allowed"
192223 aria-label = "Edit announcement"
193224 >
194- < Edit className = "h-4 w-4" />
195- Edit
225+ < Edit className = "h-3 w-3 sm:h-4 sm: w-4" />
226+ < span className = "hidden sm:inline" > Edit</ span >
196227 </ button >
197228 < button
198229 onClick = { onClose }
199230 className = "rounded-lg p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300"
200231 aria-label = "Close announcement modal"
201232 >
202- < X className = "h-6 w-6" />
233+ < X className = "h-5 w-5 sm:h-6 sm: w-6" />
203234 </ button >
204235 </ div >
205236 </ div >
206237
207238 { /* Content Area - Scrollable */ }
208- < div className = "flex-1 overflow-y-auto p-8" >
239+ < div className = "flex-1 overflow-y-auto p-4 sm:p-6 md:p- 8" >
209240 { loading ? (
210241 < div className = "flex h-full items-center justify-center" >
211242 < div className = "text-center" >
212- < div className = "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4" > </ div >
213- < p className = "text-gray-500 dark:text-gray-400" >
243+ < div className = "animate-spin rounded-full h-10 w-10 sm:h- 12 sm: w-12 border-b-2 border-blue-600 mx-auto mb-4" > </ div >
244+ < p className = "text-sm sm:text-base text- gray-500 dark:text-gray-400" >
214245 Loading announcements...
215246 </ p >
216247 </ div >
217248 </ div >
218249 ) : error ? (
219250 < div className = "flex h-full items-center justify-center" >
220- < div className = "text-center" >
221- < p className = "text-red-500 dark:text-red-400 mb-2" >
251+ < div className = "text-center px-4 " >
252+ < p className = "text-sm sm:text-base text- red-500 dark:text-red-400 mb-2" >
222253 { error }
223254 </ p >
224255 < button
225256 onClick = { ( ) => window . location . reload ( ) }
226- className = "text-sm text-blue-600 hover:underline dark:text-blue-400"
257+ className = "text-xs sm:text- sm text-blue-600 hover:underline dark:text-blue-400"
227258 >
228259 Retry
229260 </ button >
@@ -233,8 +264,8 @@ export const AnnouncementModal: React.FC<AnnouncementModalProps> = ({
233264 < AnnouncementContent page = { activeContent } />
234265 ) : (
235266 < div className = "flex h-full items-center justify-center" >
236- < div className = "text-center" >
237- < p className = "text-gray-500 dark:text-gray-400" >
267+ < div className = "text-center px-4 " >
268+ < p className = "text-sm sm:text-base text- gray-500 dark:text-gray-400" >
238269 No content available for this section yet.
239270 </ p >
240271 </ div >
0 commit comments