@@ -28,7 +28,6 @@ pub fn Modal(
2828 role="dialog"
2929 aria-modal="true"
3030 on: click=move |ev: ev:: MouseEvent | {
31- // Only close on overlay click if not a blocking modal
3231 if !is_blocking( ) {
3332 if let Some ( target) = ev. target( ) . and_then( |t| t. dyn_into:: <HtmlElement >( ) . ok( ) ) {
3433 if target. class_list( ) . contains( "modal-overlay" ) {
@@ -80,6 +79,8 @@ pub fn EmbedModal(
8079 smart_link : MaybeSignal < String > ,
8180 vip_link : MaybeSignal < String > ,
8281 whitelist : MaybeSignal < Vec < String > > ,
82+ is_public : MaybeSignal < bool > ,
83+ on_toggle_public : Callback < bool > ,
8384 on_add_url : Callback < String > ,
8485 on_remove_url : Callback < String > ,
8586 on_close : Callback < ( ) > ,
@@ -104,6 +105,8 @@ pub fn EmbedModal(
104105 let on_close = on_close. clone( ) ;
105106 let on_add_url = on_add_url. clone( ) ;
106107 let on_remove_url = on_remove_url. clone( ) ;
108+ let is_public = is_public. clone( ) ;
109+ let on_toggle_public = on_toggle_public. clone( ) ;
107110
108111 let iframe_code_for_click = iframe_code. clone( ) ;
109112 let smart_link_for_click = smart_link. clone( ) ;
@@ -118,6 +121,37 @@ pub fn EmbedModal(
118121 <button class="btn-nav" on: click=move |_| on_close. call( ( ) ) style="font-size: 1.5rem; line-height: 1;" >"×" </button>
119122 </div>
120123
124+ // --- SECTION 0: VISIBILITY TOGGLE ---
125+ <div style="background: rgba(34, 197, 94, 0.05); border: 1px solid rgba(34, 197, 94, 0.2); border-radius: 8px; padding: 16px; margin-bottom: 24px; display: flex; align-items: center; justify-content: space-between;" >
126+ <div>
127+ <div style="font-weight: 600; color: var(--text-main); margin-bottom: 4px;" >"Public Access" </div>
128+ <div style="font-size: 0.85rem; color: var(--text-muted);" >
129+ "Allow anyone to view/embed this project without whitelist restrictions."
130+ </div>
131+ </div>
132+ <label class="switch" style="position: relative; display: inline-block; width: 44px; height: 24px;" >
133+ <input type ="checkbox"
134+ checked=is_public. get( )
135+ on: change=move |ev| {
136+ let checked = event_target_checked( & ev) ;
137+ on_toggle_public. call( checked) ;
138+ }
139+ style="opacity: 0; width: 0; height: 0;"
140+ />
141+ <span class="slider"
142+ style=move || {
143+ let bg = if is_public. get( ) { "#22c55e" } else { "#3f3f46" } ;
144+ format!( "position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: {}; transition: .4s; border-radius: 34px;" , bg)
145+ }
146+ >
147+ <span style=move || {
148+ let transform = if is_public. get( ) { "translateX(20px)" } else { "translateX(0)" } ;
149+ format!( "position: absolute; content: ''; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; transform: {};" , transform)
150+ } ></span>
151+ </span>
152+ </label>
153+ </div>
154+
121155 // --- SECTION 1: IFRAME ---
122156 <div style="margin-bottom: 24px; position: relative;" >
123157 <div style="display: flex; justify-content: space-between; margin-bottom: 8px;" >
@@ -270,64 +304,75 @@ pub fn EmbedModal(
270304 </p>
271305 </div>
272306
273- // --- SECTION 4: Guest List / Whitelist ---
274- <div style="margin-bottom: 24px;" >
275- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;" >
276- <label style="color:var(--text-main); font-weight:600; font-size: 0.9rem;" >
277- "Guest List (Authorized URLs)"
278- </label>
279- <span style="font-size: 0.8rem; color: var(--text-muted);" >
280- "Only these pages can auto-launch your terminal."
281- </span>
282- </div>
283- <div style="display: flex; gap: 10px; margin-bottom: 12px;" >
284- <input
285- type ="text"
286- class="input-slug"
287- style="flex: 1;"
288- placeholder="https://medium.com/@user/article-slug"
289- prop: value=new_url
290- on: input=move |ev| set_new_url. set( event_target_value( & ev) )
291- />
292- <button
293- class="btn-primary"
294- on: click=move |_| {
295- let url = new_url. get( ) ;
296- if !url. is_empty( ) {
297- on_add_url. call( url) ;
298- set_new_url. set( String :: new( ) ) ;
299- }
300- }
301- prop: disabled=move || new_url. get( ) . is_empty( )
302- >
303- "Add URL"
304- </button>
305- </div>
306- <div style="display: flex; flex-wrap: wrap; gap: 8px;" >
307- <For
308- each=move || whitelist. get( )
309- key=|u| u. clone( )
310- children=move |url| {
311- let on_remove_url = on_remove_url. clone( ) ;
312- view! {
313- <span class="badge" style="margin: 0; display: flex; align-items: center; gap: 8px;" >
314- { url. clone( ) }
315- <button
316- class="btn-nav"
317- style="padding: 0; color: #ef4444; font-weight: bold; font-size: 0.9rem;"
318- aria-label="Remove URL"
319- on: click=move |_| {
320- on_remove_url. call( url. clone( ) ) ;
321- }
322- >
323- "×"
324- </button>
325- </span>
326- }
327- }
328- />
329- </div>
330- </div>
307+ // --- SECTION 4: Guest List / Whitelist (CONDITIONAL) ---
308+ { move || if !is_public. get( ) {
309+ let whitelist = whitelist. clone( ) ; // <--- Clone needed for Fn compliance
310+ view! {
311+ <div style="margin-bottom: 24px;" >
312+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;" >
313+ <label style="color:var(--text-main); font-weight:600; font-size: 0.9rem;" >
314+ "Guest List (Authorized URLs)"
315+ </label>
316+ <span style="font-size: 0.8rem; color: var(--text-muted);" >
317+ "Only these pages can auto-launch your terminal."
318+ </span>
319+ </div>
320+ <div style="display: flex; gap: 10px; margin-bottom: 12px;" >
321+ <input
322+ type ="text"
323+ class="input-slug"
324+ style="flex: 1;"
325+ placeholder="https://medium.com/@user/article-slug"
326+ prop: value=new_url
327+ on: input=move |ev| set_new_url. set( event_target_value( & ev) )
328+ />
329+ <button
330+ class="btn-primary"
331+ on: click=move |_| {
332+ let url = new_url. get( ) ;
333+ if !url. is_empty( ) {
334+ on_add_url. call( url) ;
335+ set_new_url. set( String :: new( ) ) ;
336+ }
337+ }
338+ prop: disabled=move || new_url. get( ) . is_empty( )
339+ >
340+ "Add URL"
341+ </button>
342+ </div>
343+ <div style="display: flex; flex-wrap: wrap; gap: 8px;" >
344+ <For
345+ each=move || whitelist. get( )
346+ key=|u| u. clone( )
347+ children=move |url| {
348+ let on_remove_url = on_remove_url. clone( ) ;
349+ view! {
350+ <span class="badge" style="margin: 0; display: flex; align-items: center; gap: 8px;" >
351+ { url. clone( ) }
352+ <button
353+ class="btn-nav"
354+ style="padding: 0; color: #ef4444; font-weight: bold; font-size: 0.9rem;"
355+ aria-label="Remove URL"
356+ on: click=move |_| {
357+ on_remove_url. call( url. clone( ) ) ;
358+ }
359+ >
360+ "×"
361+ </button>
362+ </span>
363+ }
364+ }
365+ />
366+ </div>
367+ </div>
368+ } . into_view( )
369+ } else {
370+ view! {
371+ <div style="margin-bottom: 24px; padding: 16px; border: 1px dashed var(--border); border-radius: 8px; text-align: center; color: var(--text-muted); font-size: 0.9rem;" >
372+ "Project is Public. Guest List restrictions are disabled."
373+ </div>
374+ } . into_view( )
375+ } }
331376
332377 <div class="modal-actions" >
333378 <button class="btn-secondary btn-action" on: click=move |_| on_close. call( ( ) ) >
@@ -414,4 +459,4 @@ pub fn ConfirmModal(
414459 }
415460 } }
416461 }
417- }
462+ }
0 commit comments