@zorgo/next 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.mts +9 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +343 -1
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +343 -1
- package/dist/cli.mjs.map +1 -0
- package/dist/components.d.mts +152 -1
- package/dist/components.d.ts +152 -1
- package/dist/index.d.mts +240 -3
- package/dist/index.d.ts +240 -3
- package/dist/index.js +158 -1
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +158 -1
- package/dist/index.mjs.map +1 -0
- package/dist/seo.d.mts +83 -2
- package/dist/seo.d.ts +83 -2
- package/dist/server.js +459 -1
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +422 -1
- package/dist/server.mjs.map +1 -0
- package/dist/slugs.d.mts +6 -0
- package/dist/slugs.d.ts +6 -0
- package/dist/tree/README.md +57 -0
- package/dist/tree/app/components/ModalRegistry.tsx +1 -1
- package/dist/tree/app/layout.tsx.gen.ts +4 -2
- package/dist/tree/app/locations/page.tsx +1 -1
- package/dist/tree/app/robots.ts +3 -1
- package/dist/tree/app/sitemap.ts +3 -1
- package/dist/tree/package.json.gen.ts +2 -2
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -1 +1,422 @@
|
|
|
1
|
-
const a0_0x408d24=a0_0x3494;(function(_0x42f876,_0x5be4ee){const _0xe45de9=a0_0x3494,_0x155e7f=_0x42f876();while(!![]){try{const _0x475133=-parseInt(_0xe45de9(0xc5))/0x1*(parseInt(_0xe45de9(0x10d))/0x2)+-parseInt(_0xe45de9(0x112))/0x3*(-parseInt(_0xe45de9(0x8c))/0x4)+-parseInt(_0xe45de9(0x90))/0x5*(-parseInt(_0xe45de9(0xcc))/0x6)+parseInt(_0xe45de9(0xc2))/0x7*(parseInt(_0xe45de9(0xcb))/0x8)+parseInt(_0xe45de9(0xff))/0x9+parseInt(_0xe45de9(0x11b))/0xa+-parseInt(_0xe45de9(0xd7))/0xb*(parseInt(_0xe45de9(0xfd))/0xc);if(_0x475133===_0x5be4ee)break;else _0x155e7f['push'](_0x155e7f['shift']());}catch(_0x48a711){_0x155e7f['push'](_0x155e7f['shift']());}}}(a0_0x96d4,0x19c76));import{writeFileSync,mkdirSync}from'fs';import a0_0x1245a9 from'path';import{loadEnvConfig}from'@next/env';import{z}from'zod';function priceString(_0x229bb4){const _0x39660a=a0_0x3494;return(_0x229bb4/0x64)[_0x39660a(0x83)](0x2);}function variantOffer(_0x30bc14){const _0x617250=a0_0x3494;return{'@type':_0x617250(0xb0),'price':priceString(_0x30bc14['display_price']??_0x30bc14['price']),'priceCurrency':_0x617250(0x70)};}function menuItemNode(_0x43cff9){const _0x43756a=a0_0x3494,_0x35cf94={'mCzGW':_0x43756a(0xce)},_0x4360f2={'@type':_0x35cf94[_0x43756a(0xc8)],'name':_0x43cff9[_0x43756a(0x72)]};if(_0x43cff9['description'])_0x4360f2[_0x43756a(0x109)]=_0x43cff9['description'];const _0x3e8cfb=_0x43cff9[_0x43756a(0x11a)][_0x43756a(0x118)](variantOffer);if(_0x3e8cfb[_0x43756a(0xd6)]===0x1)_0x4360f2[_0x43756a(0x88)]=_0x3e8cfb[0x0];else{if(_0x3e8cfb[_0x43756a(0xd6)]>0x1)_0x4360f2[_0x43756a(0x88)]=_0x3e8cfb;}return _0x4360f2;}function a0_0x3494(_0x18ae22,_0x32af3c){_0x18ae22=_0x18ae22-0x6d;const _0x96d4c1=a0_0x96d4();let _0x349408=_0x96d4c1[_0x18ae22];if(a0_0x3494['qhpJXq']===undefined){var _0x380154=function(_0x54ba07){const _0x4b7ad2='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x2978f0='',_0x47c83b='';for(let _0x322f99=0x0,_0x44bbcb,_0x12f5fe,_0x5c46a3=0x0;_0x12f5fe=_0x54ba07['charAt'](_0x5c46a3++);~_0x12f5fe&&(_0x44bbcb=_0x322f99%0x4?_0x44bbcb*0x40+_0x12f5fe:_0x12f5fe,_0x322f99++%0x4)?_0x2978f0+=String['fromCharCode'](0xff&_0x44bbcb>>(-0x2*_0x322f99&0x6)):0x0){_0x12f5fe=_0x4b7ad2['indexOf'](_0x12f5fe);}for(let _0x345572=0x0,_0x46f15e=_0x2978f0['length'];_0x345572<_0x46f15e;_0x345572++){_0x47c83b+='%'+('00'+_0x2978f0['charCodeAt'](_0x345572)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x47c83b);};a0_0x3494['sltHKU']=_0x380154,a0_0x3494['pdPEmH']={},a0_0x3494['qhpJXq']=!![];}const _0x327db7=_0x96d4c1[0x0],_0xe3dd6b=_0x18ae22+_0x327db7,_0x214ea3=a0_0x3494['pdPEmH'][_0xe3dd6b];return!_0x214ea3?(_0x349408=a0_0x3494['sltHKU'](_0x349408),a0_0x3494['pdPEmH'][_0xe3dd6b]=_0x349408):_0x349408=_0x214ea3,_0x349408;}function buildRestaurantJsonLd(_0x292a48,_0x3080b8){const _0x3cbd36=a0_0x3494,_0x5a435a={'Dlrbg':_0x3cbd36(0xbb),'oxKDz':_0x3cbd36(0x113)},_0x28c334=_0x292a48[_0x3cbd36(0x94)][_0x3cbd36(0x118)](_0x2142f7=>{const _0x373f02=_0x3cbd36,_0x2a0b98={'@type':_0x373f02(0xa1),'name':_0x2142f7[_0x373f02(0x72)]},_0x3e990c=_0x292a48[_0x373f02(0xa7)][_0x373f02(0xb9)](_0x17db6c=>_0x17db6c[_0x373f02(0xd1)]===_0x2142f7['category_id'])['map'](menuItemNode);if(_0x3e990c[_0x373f02(0xd6)])_0x2a0b98[_0x373f02(0xcd)]=_0x3e990c;return _0x2a0b98;}),_0x4a361e={'@context':_0x3cbd36(0xea),'@type':_0x5a435a['Dlrbg'],'name':_0x3080b8['name']};if(_0x3080b8[_0x3cbd36(0x8b)])_0x4a361e[_0x3cbd36(0x8b)]=_0x3080b8[_0x3cbd36(0x8b)];if(_0x3080b8[_0x3cbd36(0xde)])_0x4a361e['image']=_0x3080b8[_0x3cbd36(0xde)];if(_0x3080b8[_0x3cbd36(0x109)])_0x4a361e[_0x3cbd36(0x109)]=_0x3080b8[_0x3cbd36(0x109)];if(_0x3080b8['servesCuisine']?.[_0x3cbd36(0xd6)])_0x4a361e[_0x3cbd36(0x7a)]=_0x3080b8[_0x3cbd36(0x7a)];return _0x4a361e[_0x3cbd36(0x99)]={'@type':_0x5a435a[_0x3cbd36(0xe9)],'name':_0x5a435a[_0x3cbd36(0xe9)],'hasMenuSection':_0x28c334},_0x4a361e;}function apiBaseUrl(){const _0x124cd2=a0_0x3494;return process.env.NEXT_PUBLIC_API_BASE_URL??_0x124cd2(0x106);}var SiteTokenError=class extends Error{[a0_0x408d24(0xb5)];constructor(_0x4e2026,_0x24597e,_0x4d093b){const _0xf9071b=a0_0x408d24,_0xb78aa={'hxByG':_0xf9071b(0x6d)};super(_0x24597e,_0x4d093b),this[_0xf9071b(0x72)]=_0xb78aa[_0xf9071b(0x10f)],this[_0xf9071b(0xb5)]=_0x4e2026;}};async function fetchSiteToken(){const _0x415268=a0_0x408d24,_0x39f7d8={'vOHOE':_0x415268(0xf5),'tuilB':function(_0x2e4934,_0x1414b3){return _0x2e4934/_0x1414b3;},'nzZoJ':function(_0x390fc7,_0x1d9b83){return _0x390fc7(_0x1d9b83);},'VlLIl':function(_0x195f4d,_0x56f814){return _0x195f4d-_0x56f814;},'CjIGY':function(_0x1262d8,_0x88d585){return _0x1262d8+_0x88d585;},'GVuPx':function(_0xe1a768,_0x26d7d5,_0x1e77fc){return _0xe1a768(_0x26d7d5,_0x1e77fc);},'ZqGld':_0x415268(0xf2),'vpoSs':_0x415268(0x6e),'SQaoP':'NCyfj','IZehp':function(_0x36d937,_0x5093f8){return _0x36d937===_0x5093f8;},'MNWCg':'MQLUw','bIwfw':_0x415268(0xb7)},_0x300e4d=process.env.ZORGO_SITE_SECRET;if(!_0x300e4d)throw new SiteTokenError('missing-secret','ZORGO_SITE_SECRET\x20is\x20not\x20set.\x20Add\x20it\x20to\x20this\x20site\x27s\x20.env.');const _0x166333=apiBaseUrl()+_0x415268(0xa0);let _0x410a39;try{_0x410a39=await _0x39f7d8['GVuPx'](fetch,_0x166333,{'method':_0x39f7d8[_0x415268(0xc1)],'headers':{'x-zorgo-site-secret':_0x300e4d}});}catch(_0x36a9f8){throw new SiteTokenError(_0x39f7d8[_0x415268(0xaf)],_0x415268(0x95)+_0x166333+_0x415268(0xf3),{'cause':_0x36a9f8});}if(!_0x410a39['ok']){if(_0x39f7d8[_0x415268(0xc0)]==='NCyfj'){const _0x1b6784=(await _0x410a39[_0x415268(0xfe)]()[_0x415268(0x117)](()=>''))[_0x415268(0xfa)](0x0,0xc8);throw new SiteTokenError(_0x415268(0xb7),_0x415268(0xe2)+_0x410a39[_0x415268(0x84)]+'\x20'+_0x410a39[_0x415268(0x116)]+_0x415268(0xe3)+_0x166333+_0x415268(0x87)+(_0x1b6784?_0x415268(0x7f)+_0x1b6784:''));}else{const _0x32232a=_0x56c7d8[_0x415268(0x9a)](_0x24d2be[_0x415268(0x102)](),_0x415268(0x111));_0x47bd8d(_0x32232a,{'recursive':!![]}),_0x2a74a4(_0xda428e[_0x415268(0x9a)](_0x32232a,_0x39f7d8[_0x415268(0x8f)]),_0x415268(0xac)+_0x545d0c[_0x415268(0x8e)](_0x1ad2ba,null,0x2)+';\x0a');}}const {token:_0x5b7660}=await _0x410a39[_0x415268(0xd0)]();if(!_0x5b7660){if(_0x39f7d8[_0x415268(0xb1)](_0x415268(0xdd),_0x39f7d8[_0x415268(0xbe)])){const _0x228554=_0x4d1027(_0x35a6a9);if(!_0x228554)return _0x176376;const _0x4efebe=_0x4eeb48(_0x228554),_0x526d56=_0x415588[_0x415268(0xd3)](_0x39f7d8[_0x415268(0x10a)](_0x4efebe,0x4));return _0x39f7d8[_0x415268(0xd4)](_0x2ddc13,{'r':_0x5665c5[_0x415268(0x91)](0x0,_0x39f7d8['VlLIl'](_0x39f7d8[_0x415268(0xc9)](_0x228554['r'],_0x49822c),_0x526d56)),'g':_0x1f96d1[_0x415268(0x91)](0x0,_0x39f7d8[_0x415268(0xc9)](_0x228554['g'],_0xa56030)),'b':_0x25d759['max'](0x0,_0x39f7d8[_0x415268(0xef)](_0x39f7d8[_0x415268(0xc9)](_0x228554['b'],_0x3a912d),_0x4efebe))});}else throw new SiteTokenError(_0x39f7d8[_0x415268(0x101)],_0x415268(0xaa)+_0x166333+'.');}return _0x5b7660;}var BrandingSchema=z['object']({'backgroundBase':z['string']()['default'](a0_0x408d24(0x93)),'primary':z[a0_0x408d24(0xd5)]()[a0_0x408d24(0x10c)]('#3264FF'),'accent':z[a0_0x408d24(0xd5)]()[a0_0x408d24(0x10c)](a0_0x408d24(0x85)),'fontFamily':z[a0_0x408d24(0xd5)]()[a0_0x408d24(0x10c)]('Inter')})[a0_0x408d24(0x71)]({}),FranchiseSchema=z[a0_0x408d24(0xe7)]({'name':z[a0_0x408d24(0xd5)]()[a0_0x408d24(0x97)]()[a0_0x408d24(0xe4)](_0x162933=>_0x162933??process.env.ZORGO_PROJECT_NAME??a0_0x408d24(0xbb)),'site_url':z[a0_0x408d24(0xd5)]()['nullish']()[a0_0x408d24(0xe4)](_0x4a6829=>_0x4a6829??void 0x0)})['prefault']({}),HoursSchema=z[a0_0x408d24(0xa2)](z['unknown']())['prefault']([]),MenuSchema=z[a0_0x408d24(0xe7)]({'items':z[a0_0x408d24(0xa2)](z[a0_0x408d24(0xa3)]())[a0_0x408d24(0x10c)]([]),'categories':z[a0_0x408d24(0xa2)](z[a0_0x408d24(0xa3)]())[a0_0x408d24(0x10c)]([]),'components':z[a0_0x408d24(0xa2)](z[a0_0x408d24(0xa3)]())[a0_0x408d24(0x10c)]([]),'rules':z[a0_0x408d24(0xa2)](z[a0_0x408d24(0xa3)]())['default']([])});async function fetchData(_0x463d85,_0x36f040,_0x4029f4){const _0x3b1296=a0_0x408d24;try{const _0x60b6a0=await fetch(''+apiBaseUrl()+_0x463d85,{'headers':{'Authorization':_0x3b1296(0xb2)+_0x36f040}});if(!_0x60b6a0['ok'])throw new Error(_0x60b6a0[_0x3b1296(0x84)]+':\x20'+await _0x60b6a0[_0x3b1296(0xfe)]());const _0xfbc8b0=await _0x60b6a0[_0x3b1296(0xd0)]();return _0x4029f4[_0x3b1296(0x7e)](_0xfbc8b0?.[_0x3b1296(0xa6)]??_0xfbc8b0);}catch(_0x4ee5ee){return console['error'](_0x3b1296(0xa8)+_0x463d85+_0x3b1296(0xf4),_0x4ee5ee),_0x4029f4[_0x3b1296(0x7e)](void 0x0);}}var FONT_ON_LIGHT=a0_0x408d24(0xc6),FONT_ON_DARK=a0_0x408d24(0xf0),BACKGROUND_SURFACE_DELTA=0xf,BACKGROUND_TEMPERATURE_BIAS=0x8;function parseHexRgb(_0x33c7f8){const _0xd51359=a0_0x408d24,_0xba0edf={'aeciC':function(_0x5dceea,_0x1fe3be){return _0x5dceea!==_0x1fe3be;}},_0x9842ef=_0x33c7f8[_0xd51359(0xb8)]()['replace'](/^#/,''),_0x220f80=_0x9842ef['length']===0x3?_0x9842ef[_0xd51359(0x9b)]('')[_0xd51359(0x118)](_0x188e42=>_0x188e42+_0x188e42)[_0xd51359(0x9a)](''):_0x9842ef;if(_0xba0edf[_0xd51359(0xe8)](_0x220f80[_0xd51359(0xd6)],0x6)||Number[_0xd51359(0x8a)](Number[_0xd51359(0xad)](_0x220f80,0x10)))return null;return{'r':Number[_0xd51359(0xad)](_0x220f80[_0xd51359(0xfa)](0x0,0x2),0x10),'g':Number[_0xd51359(0xad)](_0x220f80[_0xd51359(0xfa)](0x2,0x4),0x10),'b':Number[_0xd51359(0xad)](_0x220f80[_0xd51359(0xfa)](0x4,0x6),0x10)};}function toHex({r:_0x17192b,g:_0x336abd,b:_0x25c7be}){const _0x47e4a0=a0_0x408d24;return'#'+[_0x17192b,_0x336abd,_0x25c7be][_0x47e4a0(0x118)](_0xbd118e=>_0xbd118e[_0x47e4a0(0xd8)](0x10)[_0x47e4a0(0xba)](0x2,'0'))[_0x47e4a0(0x9a)]('');}function relativeLuminance({r:_0x44f427,g:_0x4d5e69,b:_0x2df746}){const _0x29eb0a={'uHNys':function(_0x56cc4f,_0x28e56f){return _0x56cc4f+_0x28e56f;}};return(_0x29eb0a['uHNys'](0.299*_0x44f427,0.587*_0x4d5e69)+0.114*_0x2df746)/0xff;}function temperatureBiasFor({r:_0xb46385,g:_0x45d123,b:_0x160df2}){const _0x403cd0=a0_0x408d24,_0x39746e={'bcMUs':function(_0x236621,_0x4f0d2b){return _0x236621*_0x4f0d2b;},'xRvvK':function(_0xb9220e,_0xd8a796){return _0xb9220e(_0xd8a796);}};return Math[_0x403cd0(0xd3)](_0x39746e[_0x403cd0(0xa4)](BACKGROUND_TEMPERATURE_BIAS,_0x39746e[_0x403cd0(0x7c)](relativeLuminance,{'r':_0xb46385,'g':_0x45d123,'b':_0x160df2})));}function deriveFontColor(_0x5cf357){const _0x5749db=a0_0x408d24,_0x197d06={'DhnEO':function(_0x2969e2,_0x2618c4){return _0x2969e2>_0x2618c4;}},_0x30b8da=parseHexRgb(_0x5cf357);if(!_0x30b8da)return FONT_ON_LIGHT;return _0x197d06[_0x5749db(0x7b)](relativeLuminance(_0x30b8da),0.5)?FONT_ON_LIGHT:FONT_ON_DARK;}function deriveElevatedBackground(_0x4284d1){const _0x2dded5=a0_0x408d24,_0x27f3c0={'FEIqo':function(_0x2044c4,_0x33b829){return _0x2044c4(_0x33b829);},'Wfenm':function(_0x4754e3,_0x3f26c5){return _0x4754e3/_0x3f26c5;},'YzEoU':function(_0x1151eb,_0x28be9e){return _0x1151eb+_0x28be9e;},'zmEmN':function(_0x4c2cd3,_0x373be5){return _0x4c2cd3+_0x373be5;}},_0x3e0e9b=_0x27f3c0[_0x2dded5(0x77)](parseHexRgb,_0x4284d1);if(!_0x3e0e9b)return _0x4284d1;const _0x4fc2fa=temperatureBiasFor(_0x3e0e9b),_0x3d4b49=Math['round'](_0x27f3c0[_0x2dded5(0x82)](_0x4fc2fa,0x4));return toHex({'r':Math[_0x2dded5(0x100)](0xff,_0x27f3c0['YzEoU'](_0x27f3c0['YzEoU'](_0x3e0e9b['r'],BACKGROUND_SURFACE_DELTA),_0x3d4b49)),'g':Math[_0x2dded5(0x100)](0xff,_0x3e0e9b['g']+BACKGROUND_SURFACE_DELTA),'b':Math[_0x2dded5(0x100)](0xff,_0x27f3c0[_0x2dded5(0xdf)](_0x3e0e9b['b'],BACKGROUND_SURFACE_DELTA)-_0x4fc2fa)});}function deriveRecessedBackground(_0x13f281){const _0xef19a7=a0_0x408d24,_0x2db8ed={'CEouP':function(_0x18d190,_0x417b81){return _0x18d190/_0x417b81;},'CaUUA':function(_0x157837,_0x11963b){return _0x157837(_0x11963b);},'PSDNK':function(_0x1792d4,_0x242c47){return _0x1792d4-_0x242c47;}},_0x16465f=parseHexRgb(_0x13f281);if(!_0x16465f)return _0x13f281;const _0x203056=temperatureBiasFor(_0x16465f),_0x3ced6c=Math[_0xef19a7(0xd3)](_0x2db8ed[_0xef19a7(0x76)](_0x203056,0x4));return _0x2db8ed[_0xef19a7(0xab)](toHex,{'r':Math[_0xef19a7(0x91)](0x0,_0x16465f['r']-BACKGROUND_SURFACE_DELTA-_0x3ced6c),'g':Math[_0xef19a7(0x91)](0x0,_0x2db8ed['PSDNK'](_0x16465f['g'],BACKGROUND_SURFACE_DELTA)),'b':Math['max'](0x0,_0x16465f['b']-BACKGROUND_SURFACE_DELTA+_0x203056)});}function writeWranglerToml(){const _0x3abf8e=a0_0x408d24,_0xfc774={'YMZAN':function(_0x25fa60,_0x1ee916,_0x23f5ad){return _0x25fa60(_0x1ee916,_0x23f5ad);}},_0x2ed2b8=process.env.ZORGO_PROJECT_NAME??_0x3abf8e(0xe5),_0x25e354=process.env.NEXT_PUBLIC_SITE_URL??'',_0x3d48a0=process.env.NEXT_PUBLIC_API_BASE_URL??'',_0x1c8bd8=process.env.NEXT_PUBLIC_BASE_URL??_0x25e354,_0x2d65d2=process.env.NEXT_PUBLIC_SUPABASE_URL??'';let _0x2ee95f;try{_0x2ee95f=new URL(_0x25e354)[_0x3abf8e(0xca)];}catch{_0x2ee95f=void 0x0;}const _0x14b580=_0x2ee95f?_0x3abf8e(0xc7)+_0x2ee95f+'\x22\x0acustom_domain\x20=\x20true\x0a\x0a[[routes]]\x0apattern\x20=\x20\x22www.'+_0x2ee95f+'\x22\x0acustom_domain\x20=\x20true\x0a':'',_0x31c47d=_0x3abf8e(0xee)+_0x2ed2b8+'\x22\x0amain\x20=\x20\x22.open-next/worker.js\x22\x0acompatibility_date\x20=\x20\x222024-09-23\x22\x0acompatibility_flags\x20=\x20[\x22nodejs_compat\x22]\x0a\x0a[assets]\x0adirectory\x20=\x20\x22.open-next/assets\x22\x0abinding\x20=\x20\x22ASSETS\x22\x0a\x0a[vars]\x0aNEXT_PUBLIC_SUPABASE_URL\x20=\x20\x22'+_0x2d65d2+_0x3abf8e(0xd9)+_0x3d48a0+_0x3abf8e(0x74)+_0x25e354+_0x3abf8e(0x80)+_0x1c8bd8+'\x22\x0a\x0a[observability]\x0aenabled\x20=\x20true\x0a\x0a[observability.logs]\x0aenabled\x20=\x20true\x0ainvocation_logs\x20=\x20true\x0a'+_0x14b580;_0xfc774[_0x3abf8e(0x75)](writeFileSync,a0_0x1245a9['join'](process[_0x3abf8e(0x102)](),_0x3abf8e(0x107)),_0x31c47d);}async function logo({franchiseId:_0x34b6e4,token:_0x50d11d}){const _0x439d04=a0_0x408d24,_0x372b97={'GhRxi':function(_0x45d7e8){return _0x45d7e8();},'Tsoco':function(_0x65bf20,_0x409d8c,_0x14565b){return _0x65bf20(_0x409d8c,_0x14565b);},'oElxa':'logo.png'},_0xd66059=await fetch(_0x372b97['GhRxi'](apiBaseUrl)+_0x439d04(0xec)+_0x34b6e4,{'headers':{'Authorization':_0x439d04(0xb2)+_0x50d11d}});if(!_0xd66059['ok'])return;const _0x11b321=await _0xd66059[_0x439d04(0x9c)](),_0x39c22e=Buffer[_0x439d04(0x96)](_0x11b321),_0x1f7eba=a0_0x1245a9[_0x439d04(0x9a)](process['cwd'](),_0x439d04(0x9f));_0x372b97[_0x439d04(0x78)](mkdirSync,_0x1f7eba,{'recursive':!![]}),_0x372b97[_0x439d04(0x78)](writeFileSync,a0_0x1245a9[_0x439d04(0x9a)](_0x1f7eba,_0x372b97[_0x439d04(0x92)]),_0x39c22e),_0x372b97[_0x439d04(0x78)](writeFileSync,a0_0x1245a9[_0x439d04(0x9a)](_0x1f7eba,_0x439d04(0xae)),_0x39c22e);}function writeMenuModule(_0x35ac7e){const _0x1bf5f5=a0_0x408d24,_0x12176d={'HNVLW':function(_0x5d80f5,_0x165735,_0x5d8d55){return _0x5d80f5(_0x165735,_0x5d8d55);}},_0x28af75=a0_0x1245a9[_0x1bf5f5(0x9a)](process['cwd'](),_0x1bf5f5(0x111));_0x12176d[_0x1bf5f5(0xdc)](mkdirSync,_0x28af75,{'recursive':!![]}),_0x12176d[_0x1bf5f5(0xdc)](writeFileSync,a0_0x1245a9['join'](_0x28af75,'menu.ts'),_0x1bf5f5(0xac)+JSON['stringify'](_0x35ac7e,null,0x2)+';\x0a');}async function menu({franchiseId:_0x5eee9a,token:_0x41baac}){const _0x5c0718=a0_0x408d24,_0x5ec157={'oXbLC':function(_0x49ffca,_0x52a194){return _0x49ffca===_0x52a194;},'kJgrB':function(_0x2021e8,_0x13e270){return _0x2021e8(_0x13e270);}},_0x2e6bee=await fetch(apiBaseUrl()+_0x5c0718(0xe1)+_0x5eee9a,{'headers':{'Authorization':'Bearer\x20'+_0x41baac}});if(!_0x2e6bee['ok'])throw new Error(_0x5c0718(0x89)+_0x2e6bee['status']+':\x20'+await _0x2e6bee[_0x5c0718(0xfe)]());const _0x2643e9=await _0x2e6bee[_0x5c0718(0xd0)](),_0x3565b1=MenuSchema['parse'](_0x2643e9?.[_0x5c0718(0xa6)]??{});return _0x5ec157[_0x5c0718(0xb6)](_0x3565b1['items'][_0x5c0718(0xd6)],0x0)&&console[_0x5c0718(0x105)](_0x5c0718(0x103)),_0x5ec157[_0x5c0718(0xeb)](writeMenuModule,_0x3565b1),_0x3565b1;}async function jsonLd({franchiseId:_0x4efa17,token:_0x4b938b},_0x278d13){const _0xb1be91=a0_0x408d24,_0x5154b0=await fetchData(_0xb1be91(0x9e)+_0x4efa17,_0x4b938b,FranchiseSchema),_0x4e8e25=_0x5154b0[_0xb1be91(0x10b)],_0x255aa2=buildRestaurantJsonLd(_0x278d13,{'name':_0x5154b0['name'],'url':_0x4e8e25,'image':_0x4e8e25?_0x4e8e25+_0xb1be91(0x8d):void 0x0}),_0x1b5942=a0_0x1245a9[_0xb1be91(0x9a)](process[_0xb1be91(0x102)](),_0xb1be91(0x111));mkdirSync(_0x1b5942,{'recursive':!![]}),writeFileSync(a0_0x1245a9[_0xb1be91(0x9a)](_0x1b5942,_0xb1be91(0xe0)),_0xb1be91(0xbd)+JSON[_0xb1be91(0x8e)](_0x255aa2,null,0x2)+';\x0a');}async function hours({franchiseId:_0x2a83ae,token:_0x31de0a}){await fetchData('/franchise-info/hours/'+_0x2a83ae,_0x31de0a,HoursSchema);}async function styles({franchiseId:_0x332581,token:_0x2c55a5}){const _0x2e72f1=a0_0x408d24,_0x19f26d={'lEuSf':function(_0x293b9e,_0x3574f2){return _0x293b9e(_0x3574f2);},'crwLL':function(_0xa5068a,_0x3d7c50,_0xdc6d95){return _0xa5068a(_0x3d7c50,_0xdc6d95);},'qLNLE':'styles.json','HIHho':_0x2e72f1(0xfb)},_0x2ecd98=await fetchData(_0x2e72f1(0x98)+_0x332581,_0x2c55a5,BrandingSchema),_0x51c81b={..._0x2ecd98,'backgroundElevated':deriveElevatedBackground(_0x2ecd98[_0x2e72f1(0xb3)]),'backgroundRecessed':deriveRecessedBackground(_0x2ecd98[_0x2e72f1(0xb3)]),'foreground':deriveFontColor(_0x2ecd98[_0x2e72f1(0xb3)]),'primaryForeground':deriveFontColor(_0x2ecd98[_0x2e72f1(0xf7)]),'accentForeground':_0x19f26d[_0x2e72f1(0xf9)](deriveFontColor,_0x2ecd98[_0x2e72f1(0xe6)])},_0x2d1565=a0_0x1245a9['join'](process[_0x2e72f1(0x102)](),_0x2e72f1(0x110));_0x19f26d[_0x2e72f1(0xfc)](mkdirSync,_0x2d1565,{'recursive':!![]}),_0x19f26d[_0x2e72f1(0xfc)](writeFileSync,a0_0x1245a9[_0x2e72f1(0x9a)](_0x2d1565,_0x19f26d['qLNLE']),JSON['stringify'](_0x51c81b,null,0x2)),writeFileSync(a0_0x1245a9['join'](process['cwd'](),_0x19f26d[_0x2e72f1(0x119)],_0x2e72f1(0xc4)),_0x19f26d[_0x2e72f1(0xf9)](buildGlobalsCss,_0x2ecd98));}function a0_0x96d4(){const _0x405025=['AxrLBxm','ChjLyNvPBgq6ia','Ag91CNmGzMfPBgvKoG','vgHLihnPDguTDg9Rzw4Gzw5KCg9PBNqGCMv0DxjUzwqGmJaWigj1DcbUBYb0B2TLBIbHDca','q2fvvue','lY8GqvvutY1hru5fuKfuruqGyNKGC2nYAxb0CY9WCMvIDwLSzc50CYdIGjqGzg8GBM90igvKAxqUcMLTCg9YDcb0ExbLihSGtwvUDsb9igzYB20GiKb6B3jNBY9UzxH0iJSkcMv4Cg9YDcbJB25ZDcbTzw51oIbnzw51id0G','CgfYC2vjBNq','Bg9NBY5QCgC','DNbVu3m','t2zMzxi','svPLAha','qMvHCMvYia','yMfJA2DYB3vUzejHC2u','oWOGic0TyMfJA2DYB3vUzc1YzwnLC3nLzdOG','CMvHC29U','B1HItem','CMvQzwn0zwq','DhjPBq','zMLSDgvY','CgfKu3rHCNq','uMvZDgf1CMfUDa','tejOyKu','lY8GqvvutY1hru5fuKfuruqGyNKGC2nYAxb0CY9WCMvIDwLSzc50CYdIGjqGzg8GBM90igvKAxqUcMv4Cg9YDcbJB25ZDcbYzxn0yxvYyw50sNnVBKXKid0G','tu5xq2C','s3Dwtuy','u1fHB1a','wNfhBgq','mtaWndaYnhjuzK56CW','zxjYB3i','z2XVyMfSCY5JC3m','mtq3neniwMHNsG','iZbHmgeWyq','cLTBCM91DgvZxv0kCgf0DgvYBIa9ici','Bun6r1C','vMXmswW','Ag9ZDg5HBwu','ogjiBe5osq','nZa3nJq2txPZEgDI','AgfZtwvUDuL0zw0','twvUDuL0zw0','zM9UDezHBwLSEq','ANnVBG','y2f0zwDVCNLFAwq','yMfZzty0DxjS','CM91BMq','BNPAB0O','C3rYAw5N','BgvUz3rO','mty4nJnIDff5Du4','Dg9tDhjPBMC','iGPorvHux1bvqKXjq19bueLFqKftrv9vuKWGpsaI','Bg9NBYbMywLSzwq6','oWOGic0TywnJzw50oIa','se5wtfC','uvDcA0O','Aw1Hz2u','EM1fBu4','ANnVBMXKlNrZ','l2zYyw5JAgLZzs1PBMzVl21LBNuV','vgHLihnPDguTDg9Rzw4Gzw5KCg9PBNqGCMvQzwn0zwqGDgHLihjLCxvLC3qGka','ksbHDca','DhjHBNnMB3jT','EM9Yz28TC2L0zq','ywnJzw50','B2jQzwn0','ywvJAum','B3HlrhO','Ahr0Chm6lY9Zy2HLBweUB3jN','A0PNCKi','l2zYyw5JAgLZzs1PBMzVl2XVz28V','ANnVBKXKigzHAwXLzdO','iYbbvvrpluDftKvsqvrfrcbIEsbZy3jPChrZl3bYzwj1AwXKlNrZigzYB20GlMvUDIdIGjqGzg8GBM90igvKAxqUcM5HBwuGpsaI','q2Pjr1K','i2y1zJvMnq','s0fOwKS','ue9tva','lIbdAgvJAYborvHux1bvqKXjq19bueLFqKftrv9vuKWGyw5KihrOyxqGDgHLiefqssbPCYbYDw5UAw5NlG','igzHAwXLzcWGDxnPBMCGzgvMyxvSDhm6','BwvUDs50CW','C2L0zsb0B2TLBIbPCYbUB3qGysb3zwXSlwzVCM1Lzcbkv1qGkg5VihbHEwXVywqGC2vNBwvUDcK','ChjPBwfYEq','oWOGic0TChjPBwfYEtOG','Bev1u2y','C2XPy2u','yxbW','y3j3teW','mZe0ngXIvgXHrq','Dgv4Da','mtG3mJq4nKLhv01LrW','BwLU','yKL3zNC','y3DK','ChjLyNvPBgq6igzYyw5JAgLZzsbOyxmGBM8GBwvUDsbPDgvTCYb5zxqG4OcuigDLBMvYyxrPBMCGyw4Gzw1WDhKGBwvUDs4GqwrKigL0zw1ZigLUihrOzsbKyxnOyM9HCMqGyw5KihjLyNvPBgqGDg8GCg9WDwXHDguGDgHLihn0B3jLzNjVBNqU','BM96uNy','D2fYBG','Ahr0CdOVl2XVy2fSAg9ZDdOZmdaW','D3jHBMDSzxiUDg9TBa','EeDeuhO','zgvZy3jPChrPB24','DhvPBei','C2L0zv91CMW','zgvMyxvSDa','mZjgCxHHv3m','oWOGic0TywnJzw50lwzVCMvNCM91BMq6ia','AhHcEuC','ChvIBgLJl3n0EwXLCY8','BgLIl2DLBMvYyxrLzc8','mtK4m1jgveT3zW','twvUDq','D0Hjuem','zNjHBMnOAxnLx2LKCW','C3rHDhvZvgv4Da','y2f0y2G','BwfW','seLiAg8','DMfYAwfUDhm','nZe5mtbltLPXAMm','u2L0zvrVA2vUrxjYB3i','Dw5YzwfJAgfIBgu','oWOGic0TyMfJA2DYB3vUzc1LBgv2yxrLzdOG','vvne','ChjLzMf1Bhq','BMfTzq','ywXS','iGPorvHux1bvqKXjq19tsvrfx1vstca9ici','wu1Aqu4','q0vVDva','rKvjCw8','vhnVy28','Bg9N','C2vYDMvZq3vPC2LUzq','rgHUru8','Efj2DKS','CNvUBMLUzYbWCMvIDwLSzcbZy3jPChqUlI4','CgfYC2u','ifjLC3bVBNnLoIa','iGPorvHux1bvqKXjq19cqvnfx1vstca9ici','zfjbDxm','v2zLBM0','Dg9gAxHLza','C3rHDhvZ','iZK2otzgrG','BfPmBgu','lIbuAgLZihvZDwfSBhKGBwvHBNmGwK9sr09Fu0Lurv9trunsrvqGAxmGD3jVBMCGzM9YihrOAxmGzw52AxjVBM1LBNqU','B2zMzxjZ','BwvUDsbMzxrJAcbMywLSzwqG','Axnoyu4','DxjS','mZi4wvjPsevA','l2LTywDLCY9SB2DVlNbUzW','C3rYAw5NAwz5','DK9it0u','nwLlzLrlBq','Bwf4','B0vSEge','i0y1rJvgnq','y2f0zwDVCMLLCW','q291BgqGBM90ihjLywnOihrOzsbZAxrLlxrVA2vUigvUzhbVAw50igf0ia','zNjVBq','BNvSBgLZAa','l2zYyw5JAgLZzs1PBMzVl3n0EwXLCY8','AgfZtwvUDq','AM9PBG','C3bSAxq','yxjYyxLcDwzMzxi','oWOGic0TChjPBwfYEs1MB3jLz3jVDw5KoIa','l2zYyw5JAgLZzs1PBMzVl2zYyw5JAgLZzs8','ChvIBgLJl2LTywDLCY8','l2f1DgGVC2L0zs10B2TLBG','twvUDvnLy3rPB24','yxjYyxK','Dw5RBM93BG','yMnnvxm','cKbPBxbVCNqGDxjSkcjODhrWCZOVl2zVBNrZlMDVB2DSzwfWAxmUy29Tl2nZCZi/zMfTAwX5pq','zgf0yq'];a0_0x96d4=function(){return _0x405025;};return a0_0x96d4();}function buildGlobalsCss(_0x334d2a){const _0x187ea4=a0_0x408d24,_0x55857e={'xGDPz':function(_0x289aec,_0x208389){return _0x289aec(_0x208389);},'ujVaG':function(_0x212788,_0x1ece33){return _0x212788(_0x1ece33);}},_0x2f0b31=_0x334d2a[_0x187ea4(0xcf)]['replace'](/ /g,'+'),_0x41ff1d=deriveFontColor(_0x334d2a[_0x187ea4(0xb3)]),_0x259f4d=_0x55857e[_0x187ea4(0x108)](deriveFontColor,_0x334d2a[_0x187ea4(0xf7)]),_0x4cbcca=deriveFontColor(_0x334d2a[_0x187ea4(0xe6)]),_0x2df356=_0x55857e['ujVaG'](deriveElevatedBackground,_0x334d2a['backgroundBase']),_0x1dc2c9=deriveRecessedBackground(_0x334d2a[_0x187ea4(0xb3)]);return _0x187ea4(0xa5)+_0x2f0b31+':wght@400;700&display=swap\x22);\x0a@import\x20\x22tailwindcss\x22;\x0a/*\x20Tailwind\x20v4\x20skips\x20node_modules;\x20@zorgo/next\x20ships\x20un-obfuscated\x20components\x20so\x0a\x20\x20\x20their\x20class\x20names\x20are\x20scannable\x20here.\x20*/\x0a@source\x20\x22../node_modules/@zorgo/next/dist/components.mjs\x22;\x0a\x0a:root\x20{\x0a\x20\x20--background:\x20'+_0x334d2a[_0x187ea4(0xb3)]+_0x187ea4(0x6f)+_0x2df356+_0x187ea4(0xb4)+_0x1dc2c9+';\x0a\x20\x20--foreground:\x20'+_0x41ff1d+_0x187ea4(0xf8)+_0x334d2a[_0x187ea4(0xf7)]+_0x187ea4(0x9d)+_0x259f4d+_0x187ea4(0xdb)+_0x334d2a[_0x187ea4(0xe6)]+_0x187ea4(0x10e)+_0x4cbcca+';\x0a\x20\x20--font-sans:\x20\x22'+_0x334d2a[_0x187ea4(0xcf)]+'\x22,\x20sans-serif;\x0a}\x0a\x0a@theme\x20inline\x20{\x0a\x20\x20--color-background:\x20var(--background);\x0a\x20\x20--color-background-elevated:\x20var(--background-elevated);\x0a\x20\x20--color-background-recessed:\x20var(--background-recessed);\x0a\x20\x20--color-foreground:\x20var(--foreground);\x0a\x20\x20--color-primary:\x20var(--primary);\x0a\x20\x20--color-primary-foreground:\x20var(--primary-foreground);\x0a\x20\x20--color-accent:\x20var(--accent);\x0a\x20\x20--color-accent-foreground:\x20var(--accent-foreground);\x0a\x20\x20--font-sans:\x20var(--font-sans);\x0a}\x0a\x0a@layer\x20base\x20{\x0a\x20\x20button,\x0a\x20\x20[type=\x22button\x22],\x0a\x20\x20[type=\x22submit\x22],\x0a\x20\x20[type=\x22reset\x22]\x20{\x0a\x20\x20\x20\x20cursor:\x20pointer;\x0a\x20\x20}\x0a\x0a\x20\x20button:disabled,\x0a\x20\x20[type=\x22button\x22]:disabled,\x0a\x20\x20[type=\x22submit\x22]:disabled,\x0a\x20\x20[type=\x22reset\x22]:disabled\x20{\x0a\x20\x20\x20\x20cursor:\x20not-allowed;\x0a\x20\x20}\x0a}\x0a';}async function runPrebuild(){const _0x31ec5e=a0_0x408d24,_0x272079={'lZLle':_0x31ec5e(0x7d),'KAhZK':function(_0x43410e){return _0x43410e();},'LBhbE':_0x31ec5e(0xf6),'wHIPC':_0x31ec5e(0xd2),'KwVMF':function(_0x4452a6,_0x4281b6){return _0x4452a6(_0x4281b6);},'dRAus':function(_0x53e244,_0x48cf13){return _0x53e244(_0x48cf13);},'nozRv':'prebuild\x20script\x20completed'};console['log'](_0x272079[_0x31ec5e(0x86)]),loadEnvConfig(process[_0x31ec5e(0x102)]()),writeWranglerToml();const _0x69468=await _0x272079[_0x31ec5e(0xf1)](fetchSiteToken),_0x2a5e81=_0x69468[_0x31ec5e(0x9b)]('.')[0x1];if(!_0x2a5e81)throw new Error(_0x272079[_0x31ec5e(0xbc)]);const _0x4ee696=JSON[_0x31ec5e(0x7e)](Buffer['from'](_0x2a5e81,_0x272079[_0x31ec5e(0x114)])['toString']()),_0x50b4b6=_0x4ee696[_0x31ec5e(0x115)][0x0],[_0x34086f]=await Promise[_0x31ec5e(0x73)]([_0x272079[_0x31ec5e(0xbf)](menu,{'franchiseId':_0x50b4b6,'token':_0x69468}),logo({'franchiseId':_0x50b4b6,'token':_0x69468})[_0x31ec5e(0x117)](_0x5b955a=>console[_0x31ec5e(0xc3)](_0x31ec5e(0xda),_0x5b955a)),_0x272079[_0x31ec5e(0x81)](hours,{'franchiseId':_0x50b4b6,'token':_0x69468})[_0x31ec5e(0x117)](_0x176e9f=>console[_0x31ec5e(0xc3)](_0x31ec5e(0xa9),_0x176e9f)),styles({'franchiseId':_0x50b4b6,'token':_0x69468})[_0x31ec5e(0x117)](_0x252b23=>console[_0x31ec5e(0xc3)]('styles\x20failed:',_0x252b23))]);await jsonLd({'franchiseId':_0x50b4b6,'token':_0x69468},_0x34086f)[_0x31ec5e(0x117)](_0x5cc8b8=>console['error'](_0x31ec5e(0xed),_0x5cc8b8)),console[_0x31ec5e(0x79)](_0x272079[_0x31ec5e(0x104)]);}export{SiteTokenError,fetchSiteToken,runPrebuild};
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { loadEnvConfig } from "@next/env";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
// src/seo.ts
|
|
8
|
+
function priceString(cents) {
|
|
9
|
+
return (cents / 100).toFixed(2);
|
|
10
|
+
}
|
|
11
|
+
function variantOffer(variant) {
|
|
12
|
+
return {
|
|
13
|
+
"@type": "Offer",
|
|
14
|
+
price: priceString(variant.display_price ?? variant.price),
|
|
15
|
+
priceCurrency: "USD"
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function menuItemNode(item) {
|
|
19
|
+
const node = {
|
|
20
|
+
"@type": "MenuItem",
|
|
21
|
+
name: item.name
|
|
22
|
+
};
|
|
23
|
+
if (item.description) node.description = item.description;
|
|
24
|
+
const offers = item.variants.map(variantOffer);
|
|
25
|
+
if (offers.length === 1) node.offers = offers[0];
|
|
26
|
+
else if (offers.length > 1) node.offers = offers;
|
|
27
|
+
return node;
|
|
28
|
+
}
|
|
29
|
+
function buildRestaurantJsonLd(menu2, info) {
|
|
30
|
+
const hasMenuSection = menu2.categories.map((category) => {
|
|
31
|
+
const section = {
|
|
32
|
+
"@type": "MenuSection",
|
|
33
|
+
name: category.name
|
|
34
|
+
};
|
|
35
|
+
const items = menu2.items.filter((item) => item.category_id === category.category_id).map(menuItemNode);
|
|
36
|
+
if (items.length) section.hasMenuItem = items;
|
|
37
|
+
return section;
|
|
38
|
+
});
|
|
39
|
+
const restaurant = {
|
|
40
|
+
"@context": "https://schema.org",
|
|
41
|
+
"@type": "Restaurant",
|
|
42
|
+
name: info.name
|
|
43
|
+
};
|
|
44
|
+
if (info.url) restaurant.url = info.url;
|
|
45
|
+
if (info.image) restaurant.image = info.image;
|
|
46
|
+
if (info.description) restaurant.description = info.description;
|
|
47
|
+
if (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;
|
|
48
|
+
restaurant.hasMenu = {
|
|
49
|
+
"@type": "Menu",
|
|
50
|
+
name: "Menu",
|
|
51
|
+
hasMenuSection
|
|
52
|
+
};
|
|
53
|
+
return restaurant;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/server.ts
|
|
57
|
+
function apiBaseUrl() {
|
|
58
|
+
return process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:3000";
|
|
59
|
+
}
|
|
60
|
+
var SiteTokenError = class extends Error {
|
|
61
|
+
reason;
|
|
62
|
+
constructor(reason, message, options) {
|
|
63
|
+
super(message, options);
|
|
64
|
+
this.name = "SiteTokenError";
|
|
65
|
+
this.reason = reason;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
async function fetchSiteToken() {
|
|
69
|
+
const secret = process.env.ZORGO_SITE_SECRET;
|
|
70
|
+
if (!secret) {
|
|
71
|
+
throw new SiteTokenError(
|
|
72
|
+
"missing-secret",
|
|
73
|
+
"ZORGO_SITE_SECRET is not set. Add it to this site's .env."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const url = `${apiBaseUrl()}/auth/site-token`;
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await fetch(url, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "x-zorgo-site-secret": secret }
|
|
82
|
+
});
|
|
83
|
+
} catch (cause) {
|
|
84
|
+
throw new SiteTokenError(
|
|
85
|
+
"unreachable",
|
|
86
|
+
`Could not reach the site-token endpoint at ${url}. Check NEXT_PUBLIC_API_BASE_URL and that the API is running.`,
|
|
87
|
+
{ cause }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const body = (await res.text().catch(() => "")).slice(0, 200);
|
|
92
|
+
throw new SiteTokenError(
|
|
93
|
+
"rejected",
|
|
94
|
+
`The site-token endpoint rejected the request (${res.status} ${res.statusText}) at ${url}. This usually means ZORGO_SITE_SECRET is wrong for this environment.${body ? ` Response: ${body}` : ""}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const { token } = await res.json();
|
|
98
|
+
if (!token) {
|
|
99
|
+
throw new SiteTokenError(
|
|
100
|
+
"rejected",
|
|
101
|
+
`The site-token endpoint returned 200 but no token at ${url}.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return token;
|
|
105
|
+
}
|
|
106
|
+
var BrandingSchema = z.object({
|
|
107
|
+
backgroundBase: z.string().default("#F5F5F5"),
|
|
108
|
+
primary: z.string().default("#3264FF"),
|
|
109
|
+
accent: z.string().default("#9696FF"),
|
|
110
|
+
fontFamily: z.string().default("Inter")
|
|
111
|
+
// Google Font family name
|
|
112
|
+
}).prefault({});
|
|
113
|
+
var FranchiseSchema = z.object({
|
|
114
|
+
name: z.string().nullish().transform((v) => v ?? process.env.ZORGO_PROJECT_NAME ?? "Restaurant"),
|
|
115
|
+
site_url: z.string().nullish().transform((v) => v ?? void 0)
|
|
116
|
+
}).prefault({});
|
|
117
|
+
var HoursSchema = z.array(z.unknown()).prefault([]);
|
|
118
|
+
var MenuSchema = z.object({
|
|
119
|
+
items: z.array(z.unknown()).default([]),
|
|
120
|
+
categories: z.array(z.unknown()).default([]),
|
|
121
|
+
components: z.array(z.unknown()).default([]),
|
|
122
|
+
rules: z.array(z.unknown()).default([])
|
|
123
|
+
});
|
|
124
|
+
async function fetchData(endpoint, token, schema) {
|
|
125
|
+
try {
|
|
126
|
+
const res = await fetch(`${apiBaseUrl()}${endpoint}`, {
|
|
127
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
128
|
+
});
|
|
129
|
+
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
|
|
130
|
+
const body = await res.json();
|
|
131
|
+
return schema.parse(body?.data ?? body);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error(`prebuild: ${endpoint} failed, using defaults:`, err);
|
|
134
|
+
return schema.parse(void 0);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
var FONT_ON_LIGHT = "#0a0a0a";
|
|
138
|
+
var FONT_ON_DARK = "#f5f5f5";
|
|
139
|
+
var BACKGROUND_SURFACE_DELTA = 15;
|
|
140
|
+
var BACKGROUND_TEMPERATURE_BIAS = 8;
|
|
141
|
+
function parseHexRgb(background) {
|
|
142
|
+
const hex = background.trim().replace(/^#/, "");
|
|
143
|
+
const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
144
|
+
if (full.length !== 6 || Number.isNaN(Number.parseInt(full, 16))) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
r: Number.parseInt(full.slice(0, 2), 16),
|
|
149
|
+
g: Number.parseInt(full.slice(2, 4), 16),
|
|
150
|
+
b: Number.parseInt(full.slice(4, 6), 16)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function toHex({ r, g, b }) {
|
|
154
|
+
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
155
|
+
}
|
|
156
|
+
function relativeLuminance({ r, g, b }) {
|
|
157
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
158
|
+
}
|
|
159
|
+
function temperatureBiasFor({ r, g, b }) {
|
|
160
|
+
return Math.round(BACKGROUND_TEMPERATURE_BIAS * relativeLuminance({ r, g, b }));
|
|
161
|
+
}
|
|
162
|
+
function deriveFontColor(background) {
|
|
163
|
+
const rgb = parseHexRgb(background);
|
|
164
|
+
if (!rgb) return FONT_ON_LIGHT;
|
|
165
|
+
return relativeLuminance(rgb) > 0.5 ? FONT_ON_LIGHT : FONT_ON_DARK;
|
|
166
|
+
}
|
|
167
|
+
function deriveElevatedBackground(background) {
|
|
168
|
+
const rgb = parseHexRgb(background);
|
|
169
|
+
if (!rgb) return background;
|
|
170
|
+
const bias = temperatureBiasFor(rgb);
|
|
171
|
+
const warm = Math.round(bias / 4);
|
|
172
|
+
return toHex({
|
|
173
|
+
r: Math.min(255, rgb.r + BACKGROUND_SURFACE_DELTA + warm),
|
|
174
|
+
g: Math.min(255, rgb.g + BACKGROUND_SURFACE_DELTA),
|
|
175
|
+
b: Math.min(255, rgb.b + BACKGROUND_SURFACE_DELTA - bias)
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function deriveRecessedBackground(background) {
|
|
179
|
+
const rgb = parseHexRgb(background);
|
|
180
|
+
if (!rgb) return background;
|
|
181
|
+
const bias = temperatureBiasFor(rgb);
|
|
182
|
+
const cool = Math.round(bias / 4);
|
|
183
|
+
return toHex({
|
|
184
|
+
r: Math.max(0, rgb.r - BACKGROUND_SURFACE_DELTA - cool),
|
|
185
|
+
g: Math.max(0, rgb.g - BACKGROUND_SURFACE_DELTA),
|
|
186
|
+
b: Math.max(0, rgb.b - BACKGROUND_SURFACE_DELTA + bias)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function writeWranglerToml() {
|
|
190
|
+
const name = process.env.ZORGO_PROJECT_NAME ?? "zorgo-site";
|
|
191
|
+
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "";
|
|
192
|
+
const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
|
|
193
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? siteUrl;
|
|
194
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
|
195
|
+
let host;
|
|
196
|
+
try {
|
|
197
|
+
host = new URL(siteUrl).hostname;
|
|
198
|
+
} catch {
|
|
199
|
+
host = void 0;
|
|
200
|
+
}
|
|
201
|
+
const routes = host ? `
|
|
202
|
+
[[routes]]
|
|
203
|
+
pattern = "${host}"
|
|
204
|
+
custom_domain = true
|
|
205
|
+
|
|
206
|
+
[[routes]]
|
|
207
|
+
pattern = "www.${host}"
|
|
208
|
+
custom_domain = true
|
|
209
|
+
` : "";
|
|
210
|
+
const vars = {
|
|
211
|
+
NEXT_PUBLIC_SUPABASE_URL: supabaseUrl,
|
|
212
|
+
NEXT_PUBLIC_API_BASE_URL: apiUrl,
|
|
213
|
+
NEXT_PUBLIC_SITE_URL: siteUrl,
|
|
214
|
+
NEXT_PUBLIC_BASE_URL: baseUrl
|
|
215
|
+
};
|
|
216
|
+
const varsBlock = Object.entries(vars).filter(([, value]) => value !== "").map(([key, value]) => `${key} = "${value}"`).join("\n");
|
|
217
|
+
const toml = `# AUTO-GENERATED by scripts/prebuild.ts from .env \u2014 do not edit.
|
|
218
|
+
name = "${name}"
|
|
219
|
+
main = ".open-next/worker.js"
|
|
220
|
+
compatibility_date = "2024-09-23"
|
|
221
|
+
compatibility_flags = ["nodejs_compat"]
|
|
222
|
+
|
|
223
|
+
# Bundles the Cloudflare worker as part of \`wrangler deploy\` (and \`wrangler dev\`),
|
|
224
|
+
# so the deploy command stays a plain \`wrangler deploy\`. prebuild (which writes
|
|
225
|
+
# this file + lib/generated) must still run first \u2014 on Cloudflare Workers Builds
|
|
226
|
+
# that is the project's Build command; locally it is the npm scripts below.
|
|
227
|
+
[build]
|
|
228
|
+
command = "npx opennextjs-cloudflare build"
|
|
229
|
+
|
|
230
|
+
[assets]
|
|
231
|
+
directory = ".open-next/assets"
|
|
232
|
+
binding = "ASSETS"
|
|
233
|
+
|
|
234
|
+
[vars]
|
|
235
|
+
${varsBlock}
|
|
236
|
+
|
|
237
|
+
[observability]
|
|
238
|
+
enabled = true
|
|
239
|
+
|
|
240
|
+
[observability.logs]
|
|
241
|
+
enabled = true
|
|
242
|
+
invocation_logs = true
|
|
243
|
+
${routes}`;
|
|
244
|
+
writeFileSync(path.join(process.cwd(), "wrangler.toml"), toml);
|
|
245
|
+
}
|
|
246
|
+
async function logo({ franchiseId, token }) {
|
|
247
|
+
const logoResponse = await fetch(
|
|
248
|
+
`${apiBaseUrl()}/franchise-info/logo/${franchiseId}`,
|
|
249
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
250
|
+
);
|
|
251
|
+
if (!logoResponse.ok) return;
|
|
252
|
+
const arrayBuffer = await logoResponse.arrayBuffer();
|
|
253
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
254
|
+
const outDir = path.join(process.cwd(), `public/images/`);
|
|
255
|
+
mkdirSync(outDir, { recursive: true });
|
|
256
|
+
writeFileSync(path.join(outDir, "logo.png"), buffer);
|
|
257
|
+
writeFileSync(path.join(outDir, "logo.jpg"), buffer);
|
|
258
|
+
}
|
|
259
|
+
function writeMenuModule(menu2) {
|
|
260
|
+
const outDir = path.join(process.cwd(), `lib/generated/`);
|
|
261
|
+
mkdirSync(outDir, { recursive: true });
|
|
262
|
+
writeFileSync(
|
|
263
|
+
path.join(outDir, "menu.ts"),
|
|
264
|
+
`// AUTO-GENERATED by scripts/prebuild.ts \u2014 do not edit.
|
|
265
|
+
import type { Menu } from "@zorgo/next";
|
|
266
|
+
|
|
267
|
+
export const menu: Menu = ${JSON.stringify(menu2, null, 2)};
|
|
268
|
+
`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
async function menu({ franchiseId, token }) {
|
|
272
|
+
const menuResponse = await fetch(
|
|
273
|
+
`${apiBaseUrl()}/franchise-info/menu/${franchiseId}`,
|
|
274
|
+
{ headers: { Authorization: `Bearer ${token}` } }
|
|
275
|
+
);
|
|
276
|
+
if (!menuResponse.ok) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`menu fetch failed ${menuResponse.status}: ${await menuResponse.text()}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const body = await menuResponse.json();
|
|
282
|
+
const parsed = MenuSchema.parse(body?.data ?? {});
|
|
283
|
+
if (parsed.items.length === 0) {
|
|
284
|
+
console.warn(
|
|
285
|
+
"prebuild: franchise has no menu items yet \u2014 generating an empty menu. Add items in the dashboard and rebuild to populate the storefront."
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
writeMenuModule(parsed);
|
|
289
|
+
return parsed;
|
|
290
|
+
}
|
|
291
|
+
async function jsonLd({ franchiseId, token }, menuData) {
|
|
292
|
+
const franchise = await fetchData(
|
|
293
|
+
`/franchise-info/franchise/${franchiseId}`,
|
|
294
|
+
token,
|
|
295
|
+
FranchiseSchema
|
|
296
|
+
);
|
|
297
|
+
const url = franchise.site_url;
|
|
298
|
+
const data = buildRestaurantJsonLd(menuData, {
|
|
299
|
+
name: franchise.name,
|
|
300
|
+
url,
|
|
301
|
+
image: url ? `${url}/images/logo.png` : void 0
|
|
302
|
+
});
|
|
303
|
+
const outDir = path.join(process.cwd(), `lib/generated/`);
|
|
304
|
+
mkdirSync(outDir, { recursive: true });
|
|
305
|
+
writeFileSync(
|
|
306
|
+
path.join(outDir, "jsonld.ts"),
|
|
307
|
+
`// AUTO-GENERATED by scripts/prebuild.ts \u2014 do not edit.
|
|
308
|
+
export const restaurantJsonLd = ${JSON.stringify(data, null, 2)};
|
|
309
|
+
`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
async function hours({ franchiseId, token }) {
|
|
313
|
+
await fetchData(`/franchise-info/hours/${franchiseId}`, token, HoursSchema);
|
|
314
|
+
}
|
|
315
|
+
async function styles({ franchiseId, token }) {
|
|
316
|
+
const brand = await fetchData(
|
|
317
|
+
`/franchise-info/styles/${franchiseId}`,
|
|
318
|
+
token,
|
|
319
|
+
BrandingSchema
|
|
320
|
+
);
|
|
321
|
+
const branding = {
|
|
322
|
+
...brand,
|
|
323
|
+
backgroundElevated: deriveElevatedBackground(brand.backgroundBase),
|
|
324
|
+
backgroundRecessed: deriveRecessedBackground(brand.backgroundBase),
|
|
325
|
+
foreground: deriveFontColor(brand.backgroundBase),
|
|
326
|
+
primaryForeground: deriveFontColor(brand.primary),
|
|
327
|
+
accentForeground: deriveFontColor(brand.accent)
|
|
328
|
+
};
|
|
329
|
+
const stylesDir = path.join(process.cwd(), `public/styles/`);
|
|
330
|
+
mkdirSync(stylesDir, { recursive: true });
|
|
331
|
+
writeFileSync(
|
|
332
|
+
path.join(stylesDir, "styles.json"),
|
|
333
|
+
JSON.stringify(branding, null, 2)
|
|
334
|
+
);
|
|
335
|
+
writeFileSync(
|
|
336
|
+
path.join(process.cwd(), "app", "globals.css"),
|
|
337
|
+
buildGlobalsCss(brand)
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
function buildGlobalsCss(branding) {
|
|
341
|
+
const fontFamilyParam = branding.fontFamily.replace(/ /g, "+");
|
|
342
|
+
const foreground = deriveFontColor(branding.backgroundBase);
|
|
343
|
+
const primaryForeground = deriveFontColor(branding.primary);
|
|
344
|
+
const accentForeground = deriveFontColor(branding.accent);
|
|
345
|
+
const backgroundElevated = deriveElevatedBackground(branding.backgroundBase);
|
|
346
|
+
const backgroundRecessed = deriveRecessedBackground(branding.backgroundBase);
|
|
347
|
+
return `
|
|
348
|
+
@import url("https://fonts.googleapis.com/css2?family=${fontFamilyParam}:wght@400;700&display=swap");
|
|
349
|
+
@import "tailwindcss";
|
|
350
|
+
/* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so
|
|
351
|
+
their class names are scannable here. */
|
|
352
|
+
@source "../node_modules/@zorgo/next/dist/components.mjs";
|
|
353
|
+
|
|
354
|
+
:root {
|
|
355
|
+
--background: ${branding.backgroundBase};
|
|
356
|
+
--background-elevated: ${backgroundElevated};
|
|
357
|
+
--background-recessed: ${backgroundRecessed};
|
|
358
|
+
--foreground: ${foreground};
|
|
359
|
+
--primary: ${branding.primary};
|
|
360
|
+
--primary-foreground: ${primaryForeground};
|
|
361
|
+
--accent: ${branding.accent};
|
|
362
|
+
--accent-foreground: ${accentForeground};
|
|
363
|
+
--font-sans: "${branding.fontFamily}", sans-serif;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@theme inline {
|
|
367
|
+
--color-background: var(--background);
|
|
368
|
+
--color-background-elevated: var(--background-elevated);
|
|
369
|
+
--color-background-recessed: var(--background-recessed);
|
|
370
|
+
--color-foreground: var(--foreground);
|
|
371
|
+
--color-primary: var(--primary);
|
|
372
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
373
|
+
--color-accent: var(--accent);
|
|
374
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
375
|
+
--font-sans: var(--font-sans);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@layer base {
|
|
379
|
+
button,
|
|
380
|
+
[type="button"],
|
|
381
|
+
[type="submit"],
|
|
382
|
+
[type="reset"] {
|
|
383
|
+
cursor: pointer;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
button:disabled,
|
|
387
|
+
[type="button"]:disabled,
|
|
388
|
+
[type="submit"]:disabled,
|
|
389
|
+
[type="reset"]:disabled {
|
|
390
|
+
cursor: not-allowed;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
async function runPrebuild() {
|
|
396
|
+
console.log("running prebuild script...");
|
|
397
|
+
loadEnvConfig(process.cwd());
|
|
398
|
+
writeWranglerToml();
|
|
399
|
+
const token = await fetchSiteToken();
|
|
400
|
+
const encodedPayload = token.split(".")[1];
|
|
401
|
+
if (!encodedPayload) {
|
|
402
|
+
throw new Error("site token is not a well-formed JWT (no payload segment)");
|
|
403
|
+
}
|
|
404
|
+
const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString());
|
|
405
|
+
const franchiseId = payload.franchise_ids[0];
|
|
406
|
+
const [menuData] = await Promise.all([
|
|
407
|
+
menu({ franchiseId, token }),
|
|
408
|
+
logo({ franchiseId, token }).catch((err) => console.error("logo failed:", err)),
|
|
409
|
+
hours({ franchiseId, token }).catch((err) => console.error("hours failed:", err)),
|
|
410
|
+
styles({ franchiseId, token }).catch((err) => console.error("styles failed:", err))
|
|
411
|
+
]);
|
|
412
|
+
await jsonLd({ franchiseId, token }, menuData).catch(
|
|
413
|
+
(err) => console.error("jsonLd failed:", err)
|
|
414
|
+
);
|
|
415
|
+
console.log("prebuild script completed");
|
|
416
|
+
}
|
|
417
|
+
export {
|
|
418
|
+
SiteTokenError,
|
|
419
|
+
fetchSiteToken,
|
|
420
|
+
runPrebuild
|
|
421
|
+
};
|
|
422
|
+
//# sourceMappingURL=server.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/seo.ts"],"sourcesContent":["// Server-only entry (@zorgo/next/server). Node/Server-Component safe: no React,\n// no \"use client\", no client hooks — so scripts/prebuild.ts and the\n// app/api/zorgo-token route can import the token-exchange and build pipeline\n// from the package instead of scaffolding their own copies. Shipping these here\n// means a package update fixes every site, rather than each site freezing a\n// forked copy at scaffold time.\nimport { writeFileSync, mkdirSync } from \"fs\";\nimport path from \"path\";\nimport { loadEnvConfig } from \"@next/env\";\nimport { z } from \"zod\";\nimport type { Menu } from \"@zorgo/universal/utils/menuUtils\";\nimport { buildRestaurantJsonLd } from \"./seo\";\n\n// localhost fallback keeps a stray dev run from silently hitting production.\n// Read at call time, never at module load: prebuild imports this file before it\n// calls loadEnvConfig(), and ES imports are evaluated first — a module-level\n// read would freeze to the fallback before .env is loaded.\nfunction apiBaseUrl(): string {\n\treturn process.env.NEXT_PUBLIC_API_BASE_URL ?? \"http://localhost:3000\";\n}\n\n/** Why a site token could not be obtained — lets callers report precisely. */\nexport type SiteTokenFailure = \"missing-secret\" | \"rejected\" | \"unreachable\";\n\nexport class SiteTokenError extends Error {\n\treadonly reason: SiteTokenFailure;\n\tconstructor(reason: SiteTokenFailure, message: string, options?: { cause?: unknown }) {\n\t\tsuper(message, options);\n\t\tthis.name = \"SiteTokenError\";\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Exchange ZORGO_SITE_SECRET for a short-lived site token.\n *\n * Throws a {@link SiteTokenError} that distinguishes the three failure modes\n * (secret not set / endpoint rejected the secret / endpoint unreachable) rather\n * than collapsing them into one misleading \"missing secret\" message.\n */\nexport async function fetchSiteToken(): Promise<string> {\n\tconst secret = process.env.ZORGO_SITE_SECRET;\n\tif (!secret) {\n\t\tthrow new SiteTokenError(\n\t\t\t\"missing-secret\",\n\t\t\t\"ZORGO_SITE_SECRET is not set. Add it to this site's .env.\",\n\t\t);\n\t}\n\n\tconst url = `${apiBaseUrl()}/auth/site-token`;\n\tlet res: Response;\n\ttry {\n\t\tres = await fetch(url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"x-zorgo-site-secret\": secret },\n\t\t});\n\t} catch (cause) {\n\t\tthrow new SiteTokenError(\n\t\t\t\"unreachable\",\n\t\t\t`Could not reach the site-token endpoint at ${url}. Check NEXT_PUBLIC_API_BASE_URL and that the API is running.`,\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tif (!res.ok) {\n\t\tconst body = (await res.text().catch(() => \"\")).slice(0, 200);\n\t\tthrow new SiteTokenError(\n\t\t\t\"rejected\",\n\t\t\t`The site-token endpoint rejected the request (${res.status} ${res.statusText}) at ${url}. ` +\n\t\t\t\t`This usually means ZORGO_SITE_SECRET is wrong for this environment.${body ? ` Response: ${body}` : \"\"}`,\n\t\t);\n\t}\n\n\tconst { token } = (await res.json()) as { token?: string };\n\tif (!token) {\n\t\tthrow new SiteTokenError(\n\t\t\t\"rejected\",\n\t\t\t`The site-token endpoint returned 200 but no token at ${url}.`,\n\t\t);\n\t}\n\treturn token;\n}\n\n// ---------------------------------------------------------------------------\n// Prebuild pipeline\n// ---------------------------------------------------------------------------\n// Everything below runs at build time in a scaffolded site's cwd, fetching\n// franchise data and writing the generated modules/assets the site imports.\n\ninterface FetchArgs {\n\tfranchiseId: number;\n\ttoken: string;\n}\n\nconst BrandingSchema = z\n\t.object({\n\t\tbackgroundBase: z.string().default(\"#F5F5F5\"),\n\t\tprimary: z.string().default(\"#3264FF\"),\n\t\taccent: z.string().default(\"#9696FF\"),\n\t\tfontFamily: z.string().default(\"Inter\"), // Google Font family name\n\t})\n\t.prefault({});\ntype Branding = z.infer<typeof BrandingSchema>;\n\n// A freshly-created franchise commonly has null columns (no site_url set yet,\n// name not filled in). Coerce null|undefined to sensible values instead of\n// letting z.string() throw on null and blow up JSON-LD generation.\nconst FranchiseSchema = z\n\t.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.nullish()\n\t\t\t.transform((v) => v ?? process.env.ZORGO_PROJECT_NAME ?? \"Restaurant\"),\n\t\tsite_url: z\n\t\t\t.string()\n\t\t\t.nullish()\n\t\t\t.transform((v) => v ?? undefined),\n\t})\n\t.prefault({});\n\nconst HoursSchema = z.array(z.unknown()).prefault([]);\n\nconst MenuSchema = z.object({\n\titems: z.array(z.unknown()).default([]),\n\tcategories: z.array(z.unknown()).default([]),\n\tcomponents: z.array(z.unknown()).default([]),\n\trules: z.array(z.unknown()).default([]),\n});\n\nasync function fetchData<T>(\n\tendpoint: string,\n\ttoken: string,\n\tschema: z.ZodType<T>,\n): Promise<T> {\n\ttry {\n\t\tconst res = await fetch(`${apiBaseUrl()}${endpoint}`, {\n\t\t\theaders: { Authorization: `Bearer ${token}` },\n\t\t});\n\t\tif (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);\n\t\tconst body = await res.json();\n\t\treturn schema.parse(body?.data ?? body);\n\t} catch (err) {\n\t\tconsole.error(`prebuild: ${endpoint} failed, using defaults:`, err);\n\t\treturn schema.parse(undefined);\n\t}\n}\n\n// style stuff\nconst FONT_ON_LIGHT = \"#0a0a0a\";\nconst FONT_ON_DARK = \"#f5f5f5\";\n/** Neutral channel delta toward white (elevated) / black (recessed). */\nconst BACKGROUND_SURFACE_DELTA = 15;\n/**\n * Max warm/cool bias via R↔B trade (full strength on light bases).\n * Scaled down by luminance so darker colors shift less.\n */\nconst BACKGROUND_TEMPERATURE_BIAS = 8;\n\nfunction parseHexRgb(background: string): { r: number; g: number; b: number } | null {\n\tconst hex = background.trim().replace(/^#/, \"\");\n\tconst full =\n\t\thex.length === 3\n\t\t\t? hex\n\t\t\t\t\t.split(\"\")\n\t\t\t\t\t.map((c) => c + c)\n\t\t\t\t\t.join(\"\")\n\t\t\t: hex;\n\tif (full.length !== 6 || Number.isNaN(Number.parseInt(full, 16))) {\n\t\treturn null;\n\t}\n\treturn {\n\t\tr: Number.parseInt(full.slice(0, 2), 16),\n\t\tg: Number.parseInt(full.slice(2, 4), 16),\n\t\tb: Number.parseInt(full.slice(4, 6), 16),\n\t};\n}\n\nfunction toHex({ r, g, b }: { r: number; g: number; b: number }): string {\n\treturn `#${[r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\nfunction relativeLuminance({ r, g, b }: { r: number; g: number; b: number }): number {\n\treturn (0.299 * r + 0.587 * g + 0.114 * b) / 255;\n}\n\n/** Temperature bias scaled by luminance; max on light bases, near-zero on dark. */\nfunction temperatureBiasFor({ r, g, b }: { r: number; g: number; b: number }): number {\n\treturn Math.round(BACKGROUND_TEMPERATURE_BIAS * relativeLuminance({ r, g, b }));\n}\n\n/** Off-black on light backgrounds, off-white on dark. */\nfunction deriveFontColor(background: string): string {\n\tconst rgb = parseHexRgb(background);\n\tif (!rgb) return FONT_ON_LIGHT;\n\treturn relativeLuminance(rgb) > 0.5 ? FONT_ON_LIGHT : FONT_ON_DARK;\n}\n\n/** Lighter, slightly warmer surface (cards, raised panels) from backgroundBase. */\nfunction deriveElevatedBackground(background: string): string {\n\tconst rgb = parseHexRgb(background);\n\tif (!rgb) return background;\n\tconst bias = temperatureBiasFor(rgb);\n\tconst warm = Math.round(bias / 4);\n\treturn toHex({\n\t\tr: Math.min(255, rgb.r + BACKGROUND_SURFACE_DELTA + warm),\n\t\tg: Math.min(255, rgb.g + BACKGROUND_SURFACE_DELTA),\n\t\tb: Math.min(255, rgb.b + BACKGROUND_SURFACE_DELTA - bias),\n\t});\n}\n\n/** Darker, slightly cooler surface (wells, inset areas) from backgroundBase. */\nfunction deriveRecessedBackground(background: string): string {\n\tconst rgb = parseHexRgb(background);\n\tif (!rgb) return background;\n\tconst bias = temperatureBiasFor(rgb);\n\tconst cool = Math.round(bias / 4);\n\treturn toHex({\n\t\tr: Math.max(0, rgb.r - BACKGROUND_SURFACE_DELTA - cool),\n\t\tg: Math.max(0, rgb.g - BACKGROUND_SURFACE_DELTA),\n\t\tb: Math.max(0, rgb.b - BACKGROUND_SURFACE_DELTA + bias),\n\t});\n}\n\nfunction writeWranglerToml() {\n\tconst name = process.env.ZORGO_PROJECT_NAME ?? \"zorgo-site\";\n\tconst siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? \"\";\n\tconst apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? \"\";\n\tconst baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? siteUrl;\n\tconst supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? \"\";\n\n\t// custom_domain routes need a bare hostname; skip them if the site URL isn't\n\t// a parseable absolute URL (e.g. a local/placeholder value).\n\tlet host: string | undefined;\n\ttry {\n\t\thost = new URL(siteUrl).hostname;\n\t} catch {\n\t\thost = undefined;\n\t}\n\tconst routes = host\n\t\t? `\n[[routes]]\npattern = \"${host}\"\ncustom_domain = true\n\n[[routes]]\npattern = \"www.${host}\"\ncustom_domain = true\n`\n\t\t: \"\";\n\n\t// Omit empty values: opennextjs-cloudflare injects [vars] into the build env, so\n\t// an empty NEXT_PUBLIC_SITE_URL (a site piloted before its URL is set) would land\n\t// as \"\" — a defined-but-empty value that slips past the `?? localhost` fallbacks in\n\t// layout/robots/sitemap and makes new URL(\"\") throw, failing the build/deploy.\n\t// Leaving the var out keeps it `undefined` so those fallbacks fire.\n\tconst vars: Record<string, string> = {\n\t\tNEXT_PUBLIC_SUPABASE_URL: supabaseUrl,\n\t\tNEXT_PUBLIC_API_BASE_URL: apiUrl,\n\t\tNEXT_PUBLIC_SITE_URL: siteUrl,\n\t\tNEXT_PUBLIC_BASE_URL: baseUrl,\n\t};\n\tconst varsBlock = Object.entries(vars)\n\t\t.filter(([, value]) => value !== \"\")\n\t\t.map(([key, value]) => `${key} = \"${value}\"`)\n\t\t.join(\"\\n\");\n\n\tconst toml = `# AUTO-GENERATED by scripts/prebuild.ts from .env — do not edit.\nname = \"${name}\"\nmain = \".open-next/worker.js\"\ncompatibility_date = \"2024-09-23\"\ncompatibility_flags = [\"nodejs_compat\"]\n\n# Bundles the Cloudflare worker as part of \\`wrangler deploy\\` (and \\`wrangler dev\\`),\n# so the deploy command stays a plain \\`wrangler deploy\\`. prebuild (which writes\n# this file + lib/generated) must still run first — on Cloudflare Workers Builds\n# that is the project's Build command; locally it is the npm scripts below.\n[build]\ncommand = \"npx opennextjs-cloudflare build\"\n\n[assets]\ndirectory = \".open-next/assets\"\nbinding = \"ASSETS\"\n\n[vars]\n${varsBlock}\n\n[observability]\nenabled = true\n\n[observability.logs]\nenabled = true\ninvocation_logs = true\n${routes}`;\n\n\twriteFileSync(path.join(process.cwd(), \"wrangler.toml\"), toml);\n}\n\nasync function logo({ franchiseId, token }: FetchArgs) {\n\tconst logoResponse = await fetch(\n\t\t`${apiBaseUrl()}/franchise-info/logo/${franchiseId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\t// 404 (no logo) returns a JSON body — don't persist it as logo.png, or the\n\t// icon/OG image would render an error blob instead of falling back.\n\tif (!logoResponse.ok) return;\n\n\tconst arrayBuffer = await logoResponse.arrayBuffer();\n\tconst buffer = Buffer.from(arrayBuffer);\n\n\tconst outDir = path.join(process.cwd(), `public/images/`);\n\tmkdirSync(outDir, { recursive: true });\n\twriteFileSync(path.join(outDir, \"logo.png\"), buffer);\n\twriteFileSync(path.join(outDir, \"logo.jpg\"), buffer);\n}\n\nfunction writeMenuModule(menu: Menu) {\n\tconst outDir = path.join(process.cwd(), `lib/generated/`);\n\tmkdirSync(outDir, { recursive: true });\n\t// typed module so consumers get Menu-checked imports instead of an\n\t// untyped runtime fetch of menu.json.\n\twriteFileSync(\n\t\tpath.join(outDir, \"menu.ts\"),\n\t\t`// AUTO-GENERATED by scripts/prebuild.ts — do not edit.\\n` +\n\t\t\t`import type { Menu } from \"@zorgo/next\";\\n\\n` +\n\t\t\t`export const menu: Menu = ${JSON.stringify(menu, null, 2)};\\n`,\n\t);\n}\n\nasync function menu({ franchiseId, token }: FetchArgs): Promise<Menu> {\n\tconst menuResponse = await fetch(\n\t\t`${apiBaseUrl()}/franchise-info/menu/${franchiseId}`,\n\t\t{ headers: { Authorization: `Bearer ${token}` } },\n\t);\n\n\t// A non-ok response is a real failure (auth, server error) — fail loudly.\n\tif (!menuResponse.ok) {\n\t\tthrow new Error(\n\t\t\t`menu fetch failed ${menuResponse.status}: ${await menuResponse.text()}`,\n\t\t);\n\t}\n\n\tconst body = await menuResponse.json();\n\tconst parsed = MenuSchema.parse(body?.data ?? {}) as Menu;\n\n\t// An empty menu is expected for a freshly-initialized franchise that hasn't\n\t// added items yet. Ship a valid empty menu.ts and keep building — the\n\t// storefront renders its empty state instead of failing the whole build.\n\tif (parsed.items.length === 0) {\n\t\tconsole.warn(\n\t\t\t\"prebuild: franchise has no menu items yet — generating an empty menu. \" +\n\t\t\t\t\"Add items in the dashboard and rebuild to populate the storefront.\",\n\t\t);\n\t}\n\n\twriteMenuModule(parsed);\n\treturn parsed;\n}\n\n// franchise row (name, site_url) -> Restaurant JSON-LD, written as a typed\n// module the layout imports and drops into <head>.\nasync function jsonLd({ franchiseId, token }: FetchArgs, menuData: Menu) {\n\tconst franchise = await fetchData(\n\t\t`/franchise-info/franchise/${franchiseId}`,\n\t\ttoken,\n\t\tFranchiseSchema,\n\t);\n\tconst url = franchise.site_url;\n\n\tconst data = buildRestaurantJsonLd(menuData, {\n\t\tname: franchise.name,\n\t\turl,\n\t\timage: url ? `${url}/images/logo.png` : undefined,\n\t});\n\n\tconst outDir = path.join(process.cwd(), `lib/generated/`);\n\tmkdirSync(outDir, { recursive: true });\n\twriteFileSync(\n\t\tpath.join(outDir, \"jsonld.ts\"),\n\t\t`// AUTO-GENERATED by scripts/prebuild.ts — do not edit.\\n` +\n\t\t\t`export const restaurantJsonLd = ${JSON.stringify(data, null, 2)};\\n`,\n\t);\n}\n\nasync function hours({ franchiseId, token }: FetchArgs) {\n\tawait fetchData(`/franchise-info/hours/${franchiseId}`, token, HoursSchema);\n}\n\nasync function styles({ franchiseId, token }: FetchArgs) {\n\tconst brand = await fetchData(\n\t\t`/franchise-info/styles/${franchiseId}`,\n\t\ttoken,\n\t\tBrandingSchema,\n\t);\n\n\t// Same derived palette globals.css uses, persisted as JSON so the build-time\n\t// icon/OG/manifest routes stay on brand (they readFileSync this).\n\tconst branding = {\n\t\t...brand,\n\t\tbackgroundElevated: deriveElevatedBackground(brand.backgroundBase),\n\t\tbackgroundRecessed: deriveRecessedBackground(brand.backgroundBase),\n\t\tforeground: deriveFontColor(brand.backgroundBase),\n\t\tprimaryForeground: deriveFontColor(brand.primary),\n\t\taccentForeground: deriveFontColor(brand.accent),\n\t};\n\n\tconst stylesDir = path.join(process.cwd(), `public/styles/`);\n\tmkdirSync(stylesDir, { recursive: true });\n\twriteFileSync(\n\t\tpath.join(stylesDir, \"styles.json\"),\n\t\tJSON.stringify(branding, null, 2),\n\t);\n\n\twriteFileSync(\n\t\tpath.join(process.cwd(), \"app\", \"globals.css\"),\n\t\tbuildGlobalsCss(brand),\n\t);\n}\n\nfunction buildGlobalsCss(branding: Branding): string {\n\tconst fontFamilyParam = branding.fontFamily.replace(/ /g, \"+\");\n\tconst foreground = deriveFontColor(branding.backgroundBase);\n\tconst primaryForeground = deriveFontColor(branding.primary);\n\tconst accentForeground = deriveFontColor(branding.accent);\n\tconst backgroundElevated = deriveElevatedBackground(branding.backgroundBase);\n\tconst backgroundRecessed = deriveRecessedBackground(branding.backgroundBase);\n\n\treturn `\n@import url(\"https://fonts.googleapis.com/css2?family=${fontFamilyParam}:wght@400;700&display=swap\");\n@import \"tailwindcss\";\n/* Tailwind v4 skips node_modules; @zorgo/next ships un-obfuscated components so\n their class names are scannable here. */\n@source \"../node_modules/@zorgo/next/dist/components.mjs\";\n\n:root {\n --background: ${branding.backgroundBase};\n --background-elevated: ${backgroundElevated};\n --background-recessed: ${backgroundRecessed};\n --foreground: ${foreground};\n --primary: ${branding.primary};\n --primary-foreground: ${primaryForeground};\n --accent: ${branding.accent};\n --accent-foreground: ${accentForeground};\n --font-sans: \"${branding.fontFamily}\", sans-serif;\n}\n\n@theme inline {\n --color-background: var(--background);\n --color-background-elevated: var(--background-elevated);\n --color-background-recessed: var(--background-recessed);\n --color-foreground: var(--foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --font-sans: var(--font-sans);\n}\n\n@layer base {\n button,\n [type=\"button\"],\n [type=\"submit\"],\n [type=\"reset\"] {\n cursor: pointer;\n }\n\n button:disabled,\n [type=\"button\"]:disabled,\n [type=\"submit\"]:disabled,\n [type=\"reset\"]:disabled {\n cursor: not-allowed;\n }\n}\n`;\n}\n\n/**\n * Full prebuild pipeline for a scaffolded site. Loads the site's .env, exchanges\n * the site secret for a token, then fetches franchise data and writes the\n * generated modules/assets (wrangler.toml, menu.ts, jsonld.ts, styles, logo).\n * Called from the site's thin scripts/prebuild.ts wrapper.\n */\nexport async function runPrebuild(): Promise<void> {\n\tconsole.log(\"running prebuild script...\");\n\n\t// Load .env here so the wrapper stays a one-liner and can't reintroduce the\n\t// import-order bug (env must be loaded before any env read below).\n\tloadEnvConfig(process.cwd());\n\n\t// regenerate deploy config from .env before any network work\n\twriteWranglerToml();\n\n\tconst token = await fetchSiteToken();\n\n\tconst encodedPayload = token.split(\".\")[1];\n\tif (!encodedPayload) {\n\t\tthrow new Error(\"site token is not a well-formed JWT (no payload segment)\");\n\t}\n\tconst payload = JSON.parse(Buffer.from(encodedPayload, \"base64url\").toString());\n\tconst franchiseId = payload.franchise_ids[0];\n\n\t// menu + franchise feed the JSON-LD; a menu *fetch* failure breaks the build\n\t// loudly, but an empty menu (new franchise) is tolerated inside menu().\n\tconst [menuData] = await Promise.all([\n\t\tmenu({ franchiseId, token }),\n\t\tlogo({ franchiseId, token }).catch((err) => console.error(\"logo failed:\", err)),\n\t\thours({ franchiseId, token }).catch((err) => console.error(\"hours failed:\", err)),\n\t\tstyles({ franchiseId, token }).catch((err) => console.error(\"styles failed:\", err)),\n\t]);\n\n\tawait jsonLd({ franchiseId, token }, menuData).catch((err) =>\n\t\tconsole.error(\"jsonLd failed:\", err),\n\t);\n\n\tconsole.log(\"prebuild script completed\");\n}\n","import type { Menu } from \"@zorgo/universal/utils/menuUtils\";\nimport type { ItemVariant, MenuItem } from \"@zorgo/universal/interfaces\";\n\n/** Franchise-level facts for the Restaurant node. Everything but `name` is\n * optional so the schema degrades gracefully when a field is missing. */\nexport interface RestaurantInfo {\n\tname: string;\n\turl?: string;\n\timage?: string;\n\tdescription?: string;\n\tservesCuisine?: string[];\n}\n\n// cents -> \"5.49\" (schema.org Offer.price is a string in major currency units)\nfunction priceString(cents: number): string {\n\treturn (cents / 100).toFixed(2);\n}\n\nfunction variantOffer(variant: ItemVariant) {\n\treturn {\n\t\t\"@type\": \"Offer\",\n\t\tprice: priceString(variant.display_price ?? variant.price),\n\t\tpriceCurrency: \"USD\",\n\t};\n}\n\nfunction menuItemNode(item: MenuItem) {\n\tconst node: Record<string, unknown> = {\n\t\t\"@type\": \"MenuItem\",\n\t\tname: item.name,\n\t};\n\tif (item.description) node.description = item.description;\n\t// one Offer for a single price, an array when the item has size/variant prices\n\tconst offers = item.variants.map(variantOffer);\n\tif (offers.length === 1) node.offers = offers[0];\n\telse if (offers.length > 1) node.offers = offers;\n\treturn node;\n}\n\n/**\n * Builds a standalone schema.org JSON-LD object for a single menu item, for\n * embedding in that item's `/menu/[slug]` page `<head>`. Reuses `menuItemNode`\n * (the same node shape used inside the Restaurant menu) and adds the\n * `@context` a top-level node needs. Pure — safe to run at build time.\n */\nexport function buildMenuItemJsonLd(item: MenuItem) {\n\treturn { \"@context\": \"https://schema.org\", ...menuItemNode(item) };\n}\n\n/**\n * Builds a schema.org Restaurant JSON-LD object from a prebuilt Menu and\n * franchise info. Categories become MenuSections; the items in each become\n * MenuItems. Pure — same inputs, same output — so it can run at prebuild time\n * and be embedded straight into the site's <head>.\n */\nexport function buildRestaurantJsonLd(menu: Menu, info: RestaurantInfo) {\n\tconst hasMenuSection = menu.categories.map((category) => {\n\t\tconst section: Record<string, unknown> = {\n\t\t\t\"@type\": \"MenuSection\",\n\t\t\tname: category.name,\n\t\t};\n\t\tconst items = menu.items\n\t\t\t.filter((item) => item.category_id === category.category_id)\n\t\t\t.map(menuItemNode);\n\t\tif (items.length) section.hasMenuItem = items;\n\t\treturn section;\n\t});\n\n\tconst restaurant: Record<string, unknown> = {\n\t\t\"@context\": \"https://schema.org\",\n\t\t\"@type\": \"Restaurant\",\n\t\tname: info.name,\n\t};\n\tif (info.url) restaurant.url = info.url;\n\tif (info.image) restaurant.image = info.image;\n\tif (info.description) restaurant.description = info.description;\n\tif (info.servesCuisine?.length) restaurant.servesCuisine = info.servesCuisine;\n\trestaurant.hasMenu = {\n\t\t\"@type\": \"Menu\",\n\t\tname: \"Menu\",\n\t\thasMenuSection,\n\t};\n\n\treturn restaurant;\n}\n"],"mappings":"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","names":["menu","menu"]}
|
package/dist/slugs.d.mts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Builds the `/menu/[slug]` route segment for an item, e.g. "chicken-burrito-42". */
|
|
2
|
+
declare function menuItemSlug(name: string, baseItemId: number): string;
|
|
3
|
+
/** Parses the trailing numeric base_item_id off a `menuItemSlug` result. */
|
|
4
|
+
declare function baseItemIdFromSlug(slug: string): number;
|
|
5
|
+
|
|
6
|
+
export { baseItemIdFromSlug, menuItemSlug };
|
package/dist/slugs.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Builds the `/menu/[slug]` route segment for an item, e.g. "chicken-burrito-42". */
|
|
2
|
+
declare function menuItemSlug(name: string, baseItemId: number): string;
|
|
3
|
+
/** Parses the trailing numeric base_item_id off a `menuItemSlug` result. */
|
|
4
|
+
declare function baseItemIdFromSlug(slug: string): number;
|
|
5
|
+
|
|
6
|
+
export { baseItemIdFromSlug, menuItemSlug };
|
package/dist/tree/README.md
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Zorgo site
|
|
2
|
+
|
|
3
|
+
A Next.js storefront scaffolded from `@zorgo/next`, deployed to Cloudflare Workers
|
|
4
|
+
via OpenNext.
|
|
5
|
+
|
|
6
|
+
## Local development
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm dev # prebuild + next dev
|
|
10
|
+
pnpm build # prebuild + next build
|
|
11
|
+
pnpm preview # prebuild + wrangler dev (runs the OpenNext build hook)
|
|
12
|
+
pnpm cloudflare:deploy # prebuild + wrangler deploy (build + upload from your machine)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Generated files (do NOT commit)
|
|
16
|
+
|
|
17
|
+
`prebuild` (`scripts/prebuild.ts` → `@zorgo/next/server#runPrebuild`) regenerates
|
|
18
|
+
these from `.env` on every build, so they are gitignored:
|
|
19
|
+
|
|
20
|
+
- `wrangler.toml` — Worker config (name, vars, routes) derived from `.env`
|
|
21
|
+
- `lib/generated/` — menu data, JSON-LD, branding pulled from the Zorgo API
|
|
22
|
+
|
|
23
|
+
Because `wrangler.toml` is itself generated, **prebuild must run before
|
|
24
|
+
`wrangler deploy`**. The generated `wrangler.toml` contains a `[build]` hook that
|
|
25
|
+
runs the OpenNext bundling as part of `wrangler deploy` — so the deploy command
|
|
26
|
+
stays a plain `wrangler deploy`, but prebuild still has to produce the config +
|
|
27
|
+
data first.
|
|
28
|
+
|
|
29
|
+
## Cloudflare deployment (auto-deploy on commit)
|
|
30
|
+
|
|
31
|
+
`wrangler.toml` cannot express the git connection or path filtering — those are
|
|
32
|
+
**Workers Builds project settings**, configured once per site in the Cloudflare
|
|
33
|
+
dashboard (Workers & Pages → this Worker → Settings → Builds):
|
|
34
|
+
|
|
35
|
+
| Setting | Value |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| **Git repository** | Connect the repo, watch your production branch |
|
|
38
|
+
| **Root directory** | `apps/sites/<this-site>` (the folder containing this README) |
|
|
39
|
+
| **Build command** | `pnpm install && pnpm prebuild` — generates `wrangler.toml` + `lib/generated` |
|
|
40
|
+
| **Deploy command** | `npx wrangler deploy` — runs the `[build]` hook (OpenNext), then uploads |
|
|
41
|
+
| **Build watch paths** | `apps/sites/<this-site>/*` — so only commits inside this site's folder trigger a redeploy |
|
|
42
|
+
|
|
43
|
+
### Build environment variables
|
|
44
|
+
|
|
45
|
+
`.env` is gitignored, so the Cloudflare build won't see it. Set these as
|
|
46
|
+
**Build → Variables and Secrets** in the same settings page (mark `ZORGO_SITE_SECRET`
|
|
47
|
+
as a secret):
|
|
48
|
+
|
|
49
|
+
- `ZORGO_PROJECT_NAME` — the Worker name (must match the deployed Worker)
|
|
50
|
+
- `ZORGO_SITE_SECRET` — token used to fetch menu/branding from the Zorgo API
|
|
51
|
+
- `NEXT_PUBLIC_SITE_URL`
|
|
52
|
+
- `NEXT_PUBLIC_BASE_URL`
|
|
53
|
+
- `NEXT_PUBLIC_API_BASE_URL`
|
|
54
|
+
- `NEXT_PUBLIC_SUPABASE_URL`
|
|
55
|
+
|
|
56
|
+
With that in place, every push touching this site's folder runs
|
|
57
|
+
prebuild → OpenNext build → deploy automatically.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useLocation, useModal } from "@zorgo/next";
|
|
4
4
|
import { FindLocation, ItemCustomizationForm } from "@zorgo/next/components";
|
|
5
|
-
import type { Location, OrderItem } from "@zorgo/
|
|
5
|
+
import type { Location, OrderItem } from "@zorgo/next";
|
|
6
6
|
|
|
7
7
|
// Every modal your app can open lives in this map, keyed by the string you pass
|
|
8
8
|
// to `useModal().open(type, props)`. Add a component, give it a key, done.
|
|
@@ -8,8 +8,10 @@ import { Header, Footer, ModalHost } from "./components";
|
|
|
8
8
|
import { CookieConsent } from "@zorgo/next/components";
|
|
9
9
|
import { restaurantJsonLd } from "../lib/generated/jsonld";
|
|
10
10
|
export const metadata: Metadata = {
|
|
11
|
-
// resolves relative URLs (canonicals, the auto-injected opengraph-image) to absolute
|
|
12
|
-
|
|
11
|
+
// resolves relative URLs (canonicals, the auto-injected opengraph-image) to absolute.
|
|
12
|
+
// || (not ??) so an empty NEXT_PUBLIC_SITE_URL — a site piloted before its URL is
|
|
13
|
+
// set — falls back too; new URL("") throws and would break the build/deploy.
|
|
14
|
+
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000"),
|
|
13
15
|
title: {
|
|
14
16
|
default: ${JSON.stringify(opts.projectName)},
|
|
15
17
|
template: ${JSON.stringify(`%s | ${opts.projectName}`)},
|