1import {usePublicContact as __rbUseContact} from './PublicContact20260921.js'; 2import {a as interop,n as react} from './jsx-runtime-OQpaS_Dv.js'; 3const R=interop(react(),1),h=R.createElement; 4const base='/api/public/menu-sharing/'; 5const stateNames={queued:'Waiting to send',sending:'Submitting to mail service',accepted:'Accepted by mail service',uncertain:'Acceptance could not be confirmed â contact the pub before trying a new request',blocked:'Not sent â menu or mail preparation needs attention',cancelled:'Cancelled'}; 6const storageKey='rattlebone-menu-invitation-draft-v1'; 7async function api(action,body){ 8 const response=await fetch(base+action,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body),credentials:'same-origin'}); 9 let value;try{value=await response.json();}catch{throw new Error('We could not confirm the result. Retry this same request below.');} 10 if(!response.ok||value.success===false)throw Object.assign(new Error(value.message||'The request could not be completed.'),{status:response.status});return value; 11} 12function remember(value){try{if(value)sessionStorage.setItem(storageKey,JSON.stringify({at:Date.now(),...value}));else sessionStorage.removeItem(storageKey);return true;}catch{return false;}} 13function recovered(){try{const d=JSON.parse(sessionStorage.getItem(storageKey)||'null');if(d?.at>Date.now()-864e5&&d?.body?.requestId)return d;}catch{}return null;} 14const button=(label,onClick,props={})=>h('button',{type:'button',onClick,...props},label); 15function field(label,value,onChange,props={}){const id='rbmenu-'+label.toLowerCase().replace(/[^a-z0-9]+/g,'-');return h('label',{htmlFor:id},label,h('input',{id,value,onChange:e=>onChange(e.target.value),...props}));} 16function MenuJobStatus({data}){return h('div',null, 17 h('p',{className:'rbmenu-notice'},'Mail acceptance is not proof of inbox delivery. Check junk mail too.'), 18 h('ul',{className:'rbmenu-deliveries'},data.jobs.map(j=>h('li',{key:j.id},h('strong',null,j.to),h('span',null,(j.kind==='verify'?'Verification: ':'')+(stateNames[j.state]||j.state))))));} 19export function MenuShareFeature({menuId,menuName,menus}){ 20 const [open,setOpen]=R.useState(false); 21 return h('section',{className:'rbmenu-feature'},h('div',null,h('p',{className:'rbmenu-eyebrow'},'Good food. Better together.'),h('h2',null,'Make a date at the Rattlebone'),h('p',null,'Why not share a menu with one or more friends and suggest a date and time to meet? Weâll send your invitations with the menus, and you can see whoâs coming.')), 22 button('Invite friends & share menus',()=>setOpen(true)),open&&h(MenuDeliveryDialog,{mode:'share',menuId,menuName,menus,open,onClose:()=>setOpen(false)})); 23} 24export function EmailMenuDialog(props){return h(MenuDeliveryDialog,{...props,mode:'email'});} 25export function ShareMenuDialog(props){return h(MenuDeliveryDialog,{...props,mode:'share'});} 26export function MenuDeliveryDialog({mode='email',menuId,menuName,menus=[],open,onClose}){const __rbContact=__rbUseContact(); 27 const recoveredDraft=R.useMemo(()=>recovered(),[]),[form,setForm]=R.useState({mode,menuIds:menuId?[menuId]:[],email:'',name:'',guests:[{name:'',email:''}],suggestedDate:'',suggestedTime:'13:00',message:'',consent:false}), 28 [pending,setPending]=R.useState(null),[data,setData]=R.useState(null),[review,setReview]=R.useState(false),[busy,setBusy]=R.useState(false),[error,setError]=R.useState(''),[code,setCode]=R.useState(''),[hasDraft,setHasDraft]=R.useState(Boolean(recoveredDraft)),[storageOK,setStorageOK]=R.useState(true); 29 const dialog=R.useRef(null),isShare=form.mode==='share',locked=Boolean(pending||data),options=menus.length?menus:[{id:menuId,name:menuName}].filter(m=>m.id); 30 R.useEffect(()=>{if(!open)return;const previous=document.activeElement;dialog.current?.querySelector('input,button')?.focus();return()=>previous?.focus?.();},[open]); 31 R.useEffect(()=>{if(!open)return;function key(e){if(e.key==='Escape'&&!busy)onClose();if(e.key!=='Tab')return;const nodes=[...dialog.current.querySelectorAll('input:not(:disabled),button:not(:disabled),textarea:not(:disabled),a[href],select:not(:disabled)')].filter(n=>n.offsetParent!==null);if(!nodes.length)return;const first=nodes[0],last=nodes.at(-1);if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}}document.addEventListener('keydown',key);return()=>document.removeEventListener('keydown',key);},[open,busy,onClose]); 32 function save(body,result){setStorageOK(remember({body,result}));} 33 function change(key,value){if(!locked){setForm(v=>({...v,[key]:value}));setReview(false);}} 34 async function submit(){ 35 const body=pending||{...form,requestId:crypto.randomUUID()};setPending(body);save(body,data);setBusy(true);setError(''); 36 try{const result=await api('start',body);setData(result);save(body,result);} 37 catch(e){setError(e.message);if(e.status&&e.status<500&&e.status!==409){setPending(null);remember(null);}} 38 finally{setBusy(false);} 39 } 40 async function verify(){setBusy(true);setError('');try{const result=await api('verify',{requestId:data.requestId,manageToken:data.manageToken,code});setData(result);save(pending,result);}catch(e){setError(e.message);}finally{setBusy(false);}} 41 async function refresh(){setBusy(true);setError('');try{const result=await api('status',{requestId:data.requestId,manageToken:data.manageToken});setData(result);save(pending,result);}catch(e){setError(e.message);}finally{setBusy(false);}}
42 function restore(){setForm(recoveredDraft.body);setPending(recoveredDraft.body);setData(recoveredDraft.result||null);setReview(true);setHasDraft(false);} 43 const names=form.menuIds.map(id=>options.find(m=>m.id===id)?.name||data?.menus?.find(m=>m.id===id)?.name||id).join(', '); 44 if(!open)return null; 45 return h('div',{className:'rbmenu-overlay'},h('section',{className:'rbmenu-dialog',role:'dialog','aria-modal':true,'aria-labelledby':'rbmenu-title',ref:dialog}, 46 h('header',null,h('h2',{id:'rbmenu-title'},isShare?'Invite friends to the Rattlebone':'Email me a menu'),button('Close',onClose,{disabled:busy,'aria-label':'Close menu form'})), 47 hasDraft&&!locked&&h('div',{className:'rbmenu-notice'},h('p',null,'There is a menu request from earlier in this browser session. Restore it to check its result before sending again.'),button('Restore request',restore),button('Discard saved copy',()=>{remember(null);setHasDraft(false);}),h('p',{className:'rbmenu-small'},'Discarding this saved copy does not cancel messages already requested.')), 48 !storageOK&&h('p',{role:'status'},'This browser cannot retain the request. Keep this window open until the result is confirmed.'), 49 error&&h('p',{role:'alert',className:'rbmenu-error'},error), 50 !data&&h('form',{onSubmit:e=>{e.preventDefault();setReview(true);setError('');}}, 51 h('p',null,isShare?'Suggest a get-together, choose the menus and review your invitations. Weâll confirm your email before sending anything to your friends.':'Weâll send the current menu PDF to your email address.'), 52 h('fieldset',{disabled:locked||busy,hidden:review},h('legend',null,'Menus to include'),options.map(m=>h('label',{className:'rbmenu-check',key:m.id},h('input',{type:'checkbox',checked:form.menuIds.includes(m.id),onChange:e=>change('menuIds',e.target.checked?[...form.menuIds,m.id]:form.menuIds.filter(id=>id!==m.id)),disabled:!form.menuIds.includes(m.id)&&form.menuIds.length>=3}),m.name)), 53 h('div',{className:'rbmenu-grid'},field('Your name',form.name,v=>change('name',v),{required:isShare,maxLength:100,autoComplete:'name'}),field('Your email',form.email,v=>change('email',v),{required:true,type:'email',autoComplete:'email',maxLength:254})), 54 isShare&&h(R.Fragment,null, 55 h('div',{className:'rbmenu-grid'},field('Suggested date',form.suggestedDate,v=>change('suggestedDate',v),{type:'date',required:true}),field('Suggested time (UK)',form.suggestedTime,v=>change('suggestedTime',v),{type:'time',required:true})), 56 h('h3',null,'Your friends'),form.guests.map((g,i)=>h('div',{className:'rbmenu-friend',key:i},field('Friend '+(i+1)+' name',g.name,v=>change('guests',form.guests.map((x,k)=>k===i?{...x,name:v}:x)),{maxLength:100}),field('Friend '+(i+1)+' email',g.email,v=>change('guests',form.guests.map((x,k)=>k===i?{...x,email:v}:x)),{type:'email',required:true,maxLength:254}),form.guests.length>1&&button('Remove',()=>change('guests',form.guests.filter((_,k)=>k!==i)),{'aria-label':'Remove friend '+(i+1)}))), 57 form.guests.length<8&&button('Add another friend',()=>change('guests',[...form.guests,{name:'',email:''}])), 58 h('label',null,'A short message (optional)',h('textarea',{value:form.message,maxLength:600,rows:3,onChange:e=>change('message',e.target.value.replace(/[\r\n]+/g,' '))}))), 59 h('label',{className:'rbmenu-check'},h('input',{type:'checkbox',required:true,checked:form.consent,onChange:e=>change('consent',e.target.checked)}),isShare?'Send these personal invitations on my behalf. These friends expect to hear from me.':'Please email me the selected menu.'), 60 h('p',{className:'rbmenu-small'},'No one will be added to a mailing list. Invitations do not reserve a table.'), 61 !review&&h('button',{type:'submit',disabled:!form.menuIds.length},'Review '+(isShare?'invitations':'menu email'))), 62 review&&h('section',{className:'rbmenu-review','aria-label':'Review before sending'},h('h3',null,'Please check before sending'),h('p',null,names),h('p',null,'Your copy: '+form.email),isShare&&h(R.Fragment,null,h('p',null,form.suggestedDate+' at '+form.suggestedTime+' (UK time)'),h('ul',null,form.guests.map(g=>h('li',{key:g.email}
62,[g.name,g.email].filter(Boolean).join(' â ')))),form.message&&h('blockquote',null,form.message)), 63 button(busy?'Submittingâ¦':pending?'Retry this same request':isShare?'Email me a verification code':'Send my menu',submit,{disabled:busy}),!pending&&button('Edit details',()=>setReview(false)))), 64 data&&h(R.Fragment,null,h('p',{role:'status'},data.message),h(MenuJobStatus,{data}), 65 data.phase==='awaiting_verification'&&h('form',{onSubmit:e=>{e.preventDefault();verify();}},field('Six-digit email code',code,v=>setCode(v.replace(/\D/g,'').slice(0,6)),{inputMode:'numeric',autoComplete:'one-time-code',pattern:'[0-9]{6}',required:true,maxLength:6}),h('button',{type:'submit',disabled:busy||code.length!==6},busy?'Confirmingâ¦':'Confirm & send invitations')), 66 data.phase==='active'&&data.statusUrl&&h('p',null,h('a',{href:data.statusUrl},'View private invitation status')), 67 button('Refresh send status',refresh,{disabled:busy}),data.phase==='active'&&h('p',null,h('a',{href:'/book-a-table'},'Check availability & book a table'))), 68 h('p',{className:'rbmenu-small'},__rbContact.text('Need help? Call 01666 840871.')))); 69} 70 71export function MenuInvitationPage(){ 72 const [data,setData]=R.useState(null),[error,setError]=R.useState(''),[busy,setBusy]=R.useState(false); 73 const link=R.useMemo(()=>{const p=new URLSearchParams(location.hash.slice(1));for(const kind of ['manage','guest'])if(p.has(kind)){const parts=p.get(kind).split('.');return kind==='manage'?{kind,requestId:parts[0],manageToken:parts[1]}:{kind,requestId:parts[0],guestIndex:parts[1],token:parts[2]};}return null;},[]); 74 async function load(){if(!link){setError('This invitation link is incomplete. Please use the complete link in your email.');return;}setBusy(true);setError('');try{setData(await api(link.kind==='manage'?'status':'invitation',link));}catch(e){setError(e.message);}finally{setBusy(false);}} 75 R.useEffect(()=>{load();},[]); 76 async function rsvp(response){setBusy(true);setError('');try{setData(await api('rsvp',{...link,response}));}catch(e){setError(e.message);}finally{setBusy(false);}} 77 return h('section',{className:'rbmenu-invitation'},h('p',{className:'rbmenu-eyebrow'},'The Rattlebone Inn · Sherston'),h('h1',null,link?.kind==='manage'?'Your invitations':'Youâre invited'),error&&h('p',{role:'alert',className:'rbmenu-error'},error),busy&&!data&&h('p',{role:'status'},'Loading your invitationâ¦'), 78 data&&h(R.Fragment,null,h('p',null,link.kind==='guest'?data.organiserName+' would like you to join them for a meal.':'Keep this link private. It shows your invited friends and their replies.'),h('h2',null,new Date(data.suggestedDate+'T12:00:00Z').toLocaleDateString('en-GB',{weekday:'long',day:'numeric',month:'long',year:'numeric'})+' at '+data.suggestedTime),h('p',null,data.menus.map(m=>m.name).join(' · ')),data.message&&link.kind==='guest'&&h('blockquote',null,data.message),h('a',{href:'/menus'},'View current menus'), 79 link.kind==='manage'?h(R.Fragment,null,h(MenuJobStatus,{data}),h('h3',null,'Your friendsâ replies'),h('ul',{className:'rbmenu-deliveries'},data.guests.map(g=>h('li',{key:g.id},h('strong',null,g.name||g.email),h('span',null,g.email+' · '+g.rsvp)))),button('Refresh replies',load,{disabled:busy})): 80 h('div',{className:'rbmenu-response'},h('p',{role:'status'},'Your reply: '+data.guest.rsvp),['yes','maybe','no'].map(answer=>button({yes:'Yes, Iâd love to',maybe:'Maybe',no:'Sorry, I canât'}[answer],()=>rsvp(answer),{key:answer,disabled:busy,'aria-pressed':data.guest.rsvp===answer}))), 81 h('p',{className:'rbmenu-notice'},'This is a proposed get-together, not a table reservation. Agree your plans with the organiser, then check availability and book.'),h('a',{href:'/book-a-table'},'Check availability & book a table'))); 82} 83export default MenuInvitationPage;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.