@marktiderman/genesis-notifications 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +251 -2
- package/dist/blocks.d.ts +10 -0
- package/dist/blocks.js +35 -0
- package/dist/blocks.js.map +1 -0
- package/dist/chunk-2BG336II.js +124 -0
- package/dist/chunk-2BG336II.js.map +1 -0
- package/dist/chunk-7PHA3WVG.js +51 -0
- package/dist/chunk-7PHA3WVG.js.map +1 -0
- package/dist/chunk-EWZB35V6.js +113 -0
- package/dist/chunk-EWZB35V6.js.map +1 -0
- package/dist/chunk-UXNWI7OS.js +118 -0
- package/dist/chunk-UXNWI7OS.js.map +1 -0
- package/dist/email-render-CID_pXDy.d.ts +53 -0
- package/dist/email.d.ts +46 -0
- package/dist/email.js +64 -0
- package/dist/email.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +92 -0
- package/dist/native.js +170 -0
- package/dist/native.js.map +1 -0
- package/dist/react.d.ts +98 -0
- package/dist/react.js +4 -0
- package/dist/react.js.map +1 -0
- package/dist/runner.d.ts +174 -0
- package/dist/runner.js +3 -0
- package/dist/runner.js.map +1 -0
- package/dist/server.d.ts +658 -0
- package/dist/server.js +995 -0
- package/dist/server.js.map +1 -0
- package/dist/supabase.d.ts +118 -0
- package/dist/supabase.js +3 -0
- package/dist/supabase.js.map +1 -0
- package/dist/types-BilguCPW.d.ts +332 -0
- package/dist/web.d.ts +79 -0
- package/dist/web.js +233 -0
- package/dist/web.js.map +1 -0
- package/functions/genesis-notifications/index.ts +60 -0
- package/functions/genesis-notifications/notifications.ts +18 -0
- package/migrations/0001_genesis_notifications.sql +2057 -0
- package/migrations/tests/0001_genesis_notifications.test.sql +473 -0
- package/package.json +191 -3
- package/src/blocks.ts +42 -0
- package/src/core/index.ts +43 -0
- package/src/core/registry.ts +354 -0
- package/src/core/types.ts +90 -0
- package/src/core/validate.ts +73 -0
- package/src/email/index.tsx +142 -0
- package/src/index.ts +6 -0
- package/src/native/index.tsx +283 -0
- package/src/react/index.tsx +226 -0
- package/src/runner/index.ts +299 -0
- package/src/server/crypto.ts +44 -0
- package/src/server/dns.ts +106 -0
- package/src/server/email-render.ts +90 -0
- package/src/server/errors.ts +61 -0
- package/src/server/expo.ts +133 -0
- package/src/server/handler.ts +127 -0
- package/src/server/index.ts +57 -0
- package/src/server/notify.ts +100 -0
- package/src/server/rate.ts +47 -0
- package/src/server/resend.ts +143 -0
- package/src/server/runner.ts +524 -0
- package/src/server/secret-links.ts +53 -0
- package/src/server/store.ts +150 -0
- package/src/server/unsubscribe.ts +78 -0
- package/src/server/webhook.ts +55 -0
- package/src/supabase/index.ts +210 -0
- package/src/web/index.tsx +428 -0
package/dist/web.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web/index.tsx"],"names":[],"mappings":";;;;;;;;AA4CO,IAAM,iBAA0C,EAAE,MAAA,EAAQ,cAAc,IAAA,EAAM,MAAA,EAAQ,OAAO,OAAA;AAEpG,SAAS,QAAA,CAAS,GAAA,EAAa,GAAA,GAAM,IAAA,CAAK,KAAI,EAAW;AACvD,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAA,CAAO,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,IAAK,GAAI,CAAC,CAAA;AAChE,EAAA,IAAI,CAAA,GAAI,IAAI,OAAO,UAAA;AACnB,EAAA,IAAI,CAAA,GAAI,MAAM,OAAO,CAAA,EAAG,KAAK,KAAA,CAAM,CAAA,GAAI,EAAE,CAAC,CAAA,CAAA,CAAA;AAC1C,EAAA,IAAI,CAAA,GAAI,OAAO,OAAO,CAAA,EAAG,KAAK,KAAA,CAAM,CAAA,GAAI,IAAI,CAAC,CAAA,CAAA,CAAA;AAC7C,EAAA,OAAO,IAAI,IAAA,CAAK,GAAG,CAAA,CAAE,kBAAA,EAAmB;AAC1C;AASO,SAAS,mBAAA,CAAoB,EAAE,IAAA,EAAM,MAAA,EAAQ,QAAO,EAAiB;AAC1E,EAAA,uBACE,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,OAAA;AAAA,MACR,aAAA,EAAa,MAAA,IAAU,CAAA,aAAA,EAAgB,IAAA,CAAK,EAAE,CAAA,CAAA;AAAA,MAC9C,OAAA,EAAS,MAAM,MAAA,GAAS,IAAI,CAAA;AAAA,MAC5B,SAAA,EAAW,EAAA,CAAG,iGAAA,EAAmG,IAAA,CAAK,UAAU,aAAa,CAAA;AAAA,MAE7I,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAW,EAAA,CAAG,wCAAwC,IAAA,CAAK,MAAA,GAAS,YAAA,GAAe,gBAAgB,CAAA,EAAG,CAAA;AAAA,wBACxH,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,gBAAA,EACd,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,wBAAA,EAA0B,IAAA,CAAK,SAAS,eAAA,GAAkB,aAAa,CAAA,EAAI,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM,CAAA;AAAA,UACzG,IAAA,CAAK,uBAAO,GAAA,CAAC,MAAA,EAAA,EAAK,WAAU,8CAAA,EAAgD,QAAA,EAAA,IAAA,CAAK,MAAK,CAAA,GAAU;AAAA,SAAA,EACnG,CAAA;AAAA,4BACC,MAAA,EAAA,EAAK,SAAA,EAAU,0CAA0C,QAAA,EAAA,QAAA,CAAS,IAAA,CAAK,UAAU,CAAA,EAAE,CAAA;AAAA,QACnF,KAAK,MAAA,mBAAS,GAAA,CAAC,UAAK,SAAA,EAAU,SAAA,EAAU,oBAAM,CAAA,GAAU;AAAA;AAAA;AAAA,GAC3D;AAEJ;AAiBO,SAAS,iBAAiB,EAAE,MAAA,EAAQ,UAAU,KAAA,GAAQ,CAAA,EAAG,WAAU,EAA0B;AAClG,EAAA,MAAM,SAAS,cAAA,EAAe;AAC9B,EAAA,MAAM,EAAE,OAAO,QAAA,EAAU,WAAA,EAAa,aAAY,GAAI,gBAAA,CAAiB,EAAE,KAAA,EAAO,CAAA;AAChF,EAAA,MAAM,KAAA,GAAQ,MAAA,GAAS,CAAA,GAAI,CAAA,eAAA,EAAkB,MAAM,CAAA,OAAA,CAAA,GAAY,eAAA;AAC/D,EAAA,4BACG,OAAA,EAAA,EAAQ,YAAA,EAAc,CAAC,IAAA,KAAU,QAAQ,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,OAAO,IAAI,KAAK,WAAA,KAAgB,MAAA,EAC7F,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,kBAAe,OAAA,EAAO,IAAA,EACrB,QAAA,kBAAA,IAAA,CAAC,MAAA,EAAA,EAAO,SAAQ,OAAA,EAAQ,IAAA,EAAK,MAAA,EAAO,YAAA,EAAY,OAAO,aAAA,EAAY,mBAAA,EAAoB,WAAW,EAAA,CAAG,UAAA,EAAY,SAAS,CAAA,EACxH,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,IAAA,EAAA,EAAK,aAAA,EAAW,IAAA,EAAC,SAAA,EAAU,SAAA,EAAU,CAAA;AAAA,MACrC,MAAA,GAAS,CAAA,mBACR,GAAA,CAAC,KAAA,EAAA,EAAM,aAAA,EAAY,yBAAA,EAA0B,SAAA,EAAU,kEAAA,EACpD,QAAA,EAAA,MAAA,GAAS,EAAA,GAAK,KAAA,GAAQ,MAAA,EACzB,CAAA,GACE;AAAA,KAAA,EACN,CAAA,EACF,CAAA;AAAA,oBACA,IAAA,CAAC,cAAA,EAAA,EAAe,KAAA,EAAM,KAAA,EAAM,WAAU,UAAA,EACpC,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,6CAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,QAAA,EAAA,eAAA,EAAa,CAAA;AAAA,QACpD,SAAS,CAAA,mBACR,GAAA,CAAC,UAAO,OAAA,EAAQ,MAAA,EAAO,MAAK,IAAA,EAAK,SAAA,EAAU,YAAA,EAAa,aAAA,EAAY,8BAA6B,OAAA,EAAS,MAAM,KAAK,WAAA,EAAY,EAAG,2BAEpI,CAAA,GACE;AAAA,OAAA,EACN,CAAA;AAAA,0BACC,SAAA,EAAA,EAAU,CAAA;AAAA,MACV,KAAA,CAAM,WAAW,CAAA,mBAChB,GAAA,CAAC,cAAW,KAAA,EAAM,kBAAA,EAAmB,WAAA,EAAY,uCAAA,EAAwC,SAAA,EAAU,MAAA,EAAO,oBAE1G,GAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,UAAA,EACpB,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,KAAA,EACZ,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,qBACV,GAAA;AAAA,QAAC,mBAAA;AAAA,QAAA;AAAA,UAEC,IAAA;AAAA,UACA,MAAA,EAAQ,CAAC,CAAA,KAAM;AACb,YAAA,IAAI,CAAA,CAAE,MAAA,EAAQ,KAAK,QAAA,CAAS,EAAE,EAAE,CAAA;AAChC,YAAA,MAAA,GAAS,CAAC,CAAA;AAAA,UACZ;AAAA,SAAA;AAAA,QALK,IAAA,CAAK;AAAA,OAOb,GACH,CAAA,EACF,CAAA;AAAA,MAED,2BACC,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,CAAA;AAAA,wBACX,GAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,OAAA,EAAQ,SAAA,EAAU,yBAAwB,aAAA,EAAY,2BAAA,EAA4B,OAAA,EAAS,QAAA,EAAU,QAAA,EAAA,SAAA,EAErH;AAAA,OAAA,EACF,CAAA,GACE;AAAA,KAAA,EACN;AAAA,GAAA,EACF,CAAA;AAEJ;AAaO,SAAS,iBAAiB,EAAE,MAAA,EAAQ,QAAQ,GAAA,EAAK,KAAA,GAAQ,iBAAgB,EAA0B;AACxG,EAAA,MAAM,EAAE,KAAA,EAAO,SAAA,EAAW,KAAA,EAAO,QAAA,EAAU,WAAA,EAAa,OAAA,EAAQ,GAAI,gBAAA,CAAiB,EAAE,KAAA,EAAO,CAAA;AAC9F,EAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAC5C,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,aAAA,EAAY,mBAAA,EAChB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,UAAA,EAAA,EAAW,WAAU,sDAAA,EACpB,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,aAAW,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,MACjB,SAAA,mBACC,GAAA,CAAC,MAAA,EAAA,EAAO,OAAA,EAAQ,WAAU,IAAA,EAAK,IAAA,EAAK,aAAA,EAAY,4BAAA,EAA6B,SAAS,MAAM,KAAK,WAAA,EAAY,EAAG,2BAEhH,CAAA,GACE;AAAA,KAAA,EACN,CAAA;AAAA,wBACC,WAAA,EAAA,EAAY,SAAA,EAAU,OACpB,QAAA,EAAA,KAAA,mBACC,GAAA,CAAC,cAAW,KAAA,EAAM,6BAAA,EAA8B,WAAA,EAAY,sCAAA,EAAuC,IACjG,SAAA,mBACF,GAAA,CAAC,OAAE,SAAA,EAAU,mCAAA,EAAoC,2BAAQ,CAAA,GACvD,KAAA,CAAM,MAAA,KAAW,CAAA,uBAClB,UAAA,EAAA,EAAW,KAAA,EAAM,oBAAmB,WAAA,EAAY,uCAAA,EAAwC,oBAEzF,GAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,wBAAA,EACX,gBAAM,GAAA,CAAI,CAAC,yBACV,IAAA,CAAC,IAAA,EAAA,EAAiB,WAAU,yBAAA,EAC1B,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,mBAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,MAAA,EAAQ,CAAC,CAAA,KAAM;AACb,YAAA,IAAI,CAAA,CAAE,MAAA,EAAQ,KAAK,QAAA,CAAS,EAAE,EAAE,CAAA;AAChC,YAAA,MAAA,GAAS,CAAC,CAAA;AAAA,UACZ;AAAA;AAAA,OACF;AAAA,sBACA,GAAA,CAAC,UAAO,OAAA,EAAQ,OAAA,EAAQ,MAAK,IAAA,EAAK,YAAA,EAAY,CAAA,QAAA,EAAW,IAAA,CAAK,KAAK,CAAA,CAAA,EAAI,eAAa,CAAA,qBAAA,EAAwB,IAAA,CAAK,EAAE,CAAA,CAAA,EAAI,OAAA,EAAS,MAAM,KAAK,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA,EAAG,QAAA,EAAA,SAAA,EAE7J;AAAA,KAAA,EAAA,EAVO,IAAA,CAAK,EAWd,CACD,CAAA,EACH,CAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;AAQO,SAAS,iBAAA,CAAkB,EAAE,aAAA,GAAgB,cAAA,EAAe,EAAyD;AAC1H,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAQ,GAAA,KAAQ,cAAA,EAAe;AAC7C,EAAA,MAAM,UAAW,CAAC,QAAA,EAAU,QAAQ,OAAO,CAAA,CAAY,OAAO,CAAC,EAAA,KAAO,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,QAAA,CAAS,EAAE,CAAC,CAAC,CAAA;AAC/G,EAAA,IAAI,MAAA,KAAW,SAAS,uBAAO,GAAA,CAAC,cAAW,KAAA,EAAM,0CAAA,EAA2C,aAAY,sCAAA,EAAuC,CAAA;AAC/I,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAM,aAAA,EAAY,0BAAA,EACjB,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,WAAA,EAAA,EACC,+BAAC,QAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,aAAU,QAAA,EAAA,iBAAA,EAAe,CAAA;AAAA,MACzB,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAA,yBACX,SAAA,EAAA,EAAmB,SAAA,EAAU,aAAA,EAC3B,QAAA,EAAA,aAAA,CAAc,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAA,EAAA,EADzB,EAEhB,CACD;AAAA,KAAA,EACH,CAAA,EACF,CAAA;AAAA,wBACC,SAAA,EAAA,EACE,QAAA,EAAA,IAAA,CAAK,IAAI,CAAC,GAAA,0BACR,QAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,IAAA,CAAC,SAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EAAe,QAAA,EAAA,GAAA,CAAI,KAAA,EAAM,CAAA;AAAA,QACvC,GAAA,CAAI,8BAAc,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,+BAAA,EAAiC,QAAA,EAAA,GAAA,CAAI,aAAY,CAAA,GAAS,IAAA;AAAA,QAC3F,IAAI,QAAA,mBAAW,GAAA,CAAC,SAAI,SAAA,EAAU,+BAAA,EAAgC,uBAAS,CAAA,GAAS;AAAA,OAAA,EACnF,CAAA;AAAA,MACC,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAA,qBACZ,GAAA,CAAC,SAAA,EAAA,EAAmB,SAAA,EAAU,aAAA,EAC3B,QAAA,EAAA,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,EAAE,CAAA,mBACvB,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,YAAA,EAAY,CAAA,EAAG,GAAA,CAAI,KAAK,CAAA,EAAA,EAAK,cAAc,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAC,CAAA,CAAA;AAAA,UACpE,aAAA,EAAa,CAAA,wBAAA,EAA2B,GAAA,CAAI,EAAE,IAAI,EAAE,CAAA,CAAA;AAAA,UACpD,OAAA,EAAS,OAAA,CAAQ,GAAA,CAAI,MAAA,CAAO,EAAE,CAAC,CAAA;AAAA,UAC/B,QAAA,EAAU,MAAA,KAAW,OAAA,IAAW,GAAA,CAAI,QAAA;AAAA,UACpC,eAAA,EAAiB,CAAC,EAAA,KAChB,KAAK,IAAI,GAAA,CAAI,EAAA,EAAI,EAAA,EAAI,EAAE,EAAE,KAAA,CAAM,MAAM,KAAA,CAAM,KAAA,CAAM,iEAAiE,CAAC;AAAA;AAAA,OAEvH,mBAEA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,uBAAA,EAAwB,cAAW,eAAA,EAAgB,QAAA,EAAA,QAAA,EAEnE,CAAA,EAAA,EAdY,EAgBhB,CACD;AAAA,KAAA,EAAA,EAxBY,GAAA,CAAI,EAyBnB,CACD,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,IAAM,cAAA,GAA2F;AAAA,EAC/F,SAAA,EAAW,SAAA;AAAA,EACX,IAAA,EAAM,SAAA;AAAA,EACN,OAAA,EAAS,MAAA;AAAA,EACT,OAAA,EAAS,MAAA;AAAA,EACT,MAAA,EAAQ,aAAA;AAAA,EACR,OAAA,EAAS,OAAA;AAAA,EACT,SAAA,EAAW;AACb,CAAA;AAQO,SAAS,WAAA,CAAY,EAAE,QAAA,EAAU,KAAA,GAAQ,KAAI,EAAiD;AACnG,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,uBAAA,EAAwB;AACnD,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,EAAE,QAAA,EAAU,CAAC,yBAAyB,MAAA,EAAQ,KAAA,EAAO,QAAA,IAAY,KAAA,EAAO,KAAK,CAAA,EAAG,SAAS,MAAM,MAAA,CAAO,WAAA,CAAY,EAAE,QAAA,EAAU,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,MAAA,KAAW,IAAA,EAAM,CAAA;AACzL,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,IAAQ,EAAC;AAC5B,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,aAAA,EAAY,2BAAA,EAChB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,UAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,aAAU,QAAA,EAAA,cAAA,EAAY,CAAA;AAAA,sBACvB,GAAA,CAAC,mBAAgB,QAAA,EAAA,2DAAA,EAAyD;AAAA,KAAA,EAC5E,CAAA;AAAA,oBACA,GAAA,CAAC,eACE,QAAA,EAAA,KAAA,CAAM,OAAA,uBACJ,UAAA,EAAA,EAAW,KAAA,EAAM,yBAAwB,WAAA,EAAY,4CAAA,EAA6C,IACjG,IAAA,CAAK,MAAA,KAAW,oBAClB,GAAA,CAAC,UAAA,EAAA,EAAW,OAAM,kBAAA,EAAmB,CAAA,wBAEpC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,WAAA,EAAA,EACC,+BAAC,QAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,aAAU,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,wBACf,GAAA,CAAC,aAAU,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,wBACf,GAAA,CAAC,aAAU,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,wBAClB,GAAA,CAAC,aAAU,QAAA,EAAA,IAAA,EAAE,CAAA;AAAA,wBACb,GAAA,CAAC,aAAU,QAAA,EAAA,QAAA,EAAM,CAAA;AAAA,wBACjB,GAAA,CAAC,aAAU,QAAA,EAAA,QAAA,EAAM;AAAA,OAAA,EACnB,CAAA,EACF,CAAA;AAAA,sBACA,GAAA,CAAC,SAAA,EAAA,EACE,QAAA,EAAA,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,qBACT,IAAA,CAAC,QAAA,EAAA,EAAoB,aAAA,EAAa,CAAA,sBAAA,EAAyB,CAAA,CAAE,EAAE,CAAA,CAAA,EAC7D,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,WAAU,2BAAA,EAA6B,QAAA,EAAA,IAAI,KAAK,CAAA,CAAE,UAAU,CAAA,CAAE,cAAA,EAAe,EAAE,CAAA;AAAA,wBAC1F,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,mBAAA,EAAqB,YAAE,IAAA,EAAK,CAAA;AAAA,4BAChD,SAAA,EAAA,EAAW,QAAA,EAAA,CAAA,CAAE,OAAA,KAAY,OAAA,GAAU,UAAU,MAAA,EAAO,CAAA;AAAA,4BACpD,SAAA,EAAA,EAAU,SAAA,EAAU,SAAA,EAAW,QAAA,EAAA,CAAA,CAAE,oBAAoB,QAAA,EAAI,CAAA;AAAA,wBAC1D,GAAA,CAAC,SAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,OAAA,EAAS,cAAA,CAAe,CAAA,CAAE,MAAM,CAAA,IAAK,OAAA,EAAU,QAAA,EAAA,CAAA,CAAE,MAAA,EAAO,CAAA,EACjE,CAAA;AAAA,wBACA,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,+BAAA,EAClB,YAAE,WAAA,IAAe,CAAA,CAAE,KAAA,KAAU,CAAA,CAAE,WAAW,CAAA,GAAI,CAAA,EAAG,CAAA,CAAE,QAAQ,cAAc,EAAA,CAAA,EAC5E;AAAA,OAAA,EAAA,EAVa,CAAA,CAAE,EAWjB,CACD,CAAA,EACH;AAAA,KAAA,EACF,CAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;AAEA,IAAM,OAAA,GAAkC,EAAE,MAAA,EAAQ,SAAA,EAAW,WAAW,gBAAA,EAAkB,WAAA,EAAa,cAAA,EAAgB,MAAA,EAAQ,mBAAA,EAAoB;AAQ5I,SAAS,eAAA,CAAgB,EAAE,KAAA,GAAQ,GAAA,EAAI,EAAuB;AACnE,EAAA,MAAM,EAAE,MAAA,EAAQ,MAAA,EAAO,GAAI,uBAAA,EAAwB;AACnD,EAAA,MAAM,cAAc,cAAA,EAAe;AACnC,EAAA,MAAM,GAAA,GAAM,CAAC,uBAAA,EAAyB,MAAA,EAAQ,gBAAgB,KAAK,CAAA;AACnE,EAAA,MAAM,QAAQ,QAAA,CAAS,EAAE,QAAA,EAAU,GAAA,EAAK,SAAS,MAAM,MAAA,CAAO,YAAA,CAAa,EAAE,OAAO,CAAA,EAAG,OAAA,EAAS,MAAA,KAAW,MAAM,CAAA;AACjH,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,IAAQ,EAAC;AAC5B,EAAA,uBACE,IAAA,CAAC,IAAA,EAAA,EAAK,aAAA,EAAY,2BAAA,EAChB,QAAA,EAAA;AAAA,oBAAA,IAAA,CAAC,UAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,aAAU,QAAA,EAAA,sBAAA,EAAoB,CAAA;AAAA,sBAC/B,GAAA,CAAC,mBAAgB,QAAA,EAAA,iFAAA,EAA+E;AAAA,KAAA,EAClG,CAAA;AAAA,oBACA,GAAA,CAAC,eACE,QAAA,EAAA,KAAA,CAAM,OAAA,uBACJ,UAAA,EAAA,EAAW,KAAA,EAAM,0BAAyB,WAAA,EAAY,mCAAA,EAAoC,IACzF,IAAA,CAAK,MAAA,KAAW,oBAClB,GAAA,CAAC,UAAA,EAAA,EAAW,OAAM,yBAAA,EAA0B,CAAA,wBAE3C,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,WAAA,EAAA,EACC,+BAAC,QAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,aAAU,QAAA,EAAA,SAAA,EAAO,CAAA;AAAA,wBAClB,GAAA,CAAC,aAAU,QAAA,EAAA,KAAA,EAAG,CAAA;AAAA,wBACd,GAAA,CAAC,aAAU,QAAA,EAAA,UAAA,EAAQ,CAAA;AAAA,wBACnB,GAAA,CAAC,aAAU,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,4BACf,SAAA,EAAA,EAAU;AAAA,OAAA,EACb,CAAA,EACF,CAAA;AAAA,0BACC,SAAA,EAAA,EACE,QAAA,EAAA,IAAA,CAAK,IAAI,CAAC,CAAA,0BACR,QAAA,EAAA,EACC,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAA,EAAA,EAAW,QAAA,EAAA,CAAA,CAAE,YAAA,IAAgB,CAAA,EAAG,CAAA,CAAE,WAAW,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,MAAA,CAAA,EAAI,CAAA;AAAA,4BAC5D,SAAA,EAAA,EAAW,QAAA,EAAA,OAAA,CAAQ,EAAE,MAAM,CAAA,IAAK,EAAE,MAAA,EAAO,CAAA;AAAA,4BACzC,SAAA,EAAA,EAAW,QAAA,EAAA,CAAA,CAAE,aAAa,GAAA,GAAM,KAAA,GAAQ,EAAE,QAAA,EAAS,CAAA;AAAA,wBACpD,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,2BAAA,EAA6B,QAAA,EAAA,IAAI,KAAK,CAAA,CAAE,UAAU,CAAA,CAAE,kBAAA,EAAmB,EAAE,CAAA;AAAA,wBAC9F,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,YAAA,EACnB,QAAA,kBAAA,GAAA;AAAA,UAAC,MAAA;AAAA,UAAA;AAAA,YACC,OAAA,EAAQ,SAAA;AAAA,YACR,IAAA,EAAK,IAAA;AAAA,YACL,eAAa,CAAA,8BAAA,EAAiC,CAAA,CAAE,WAAW,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAAA,YACvE,SAAS,YAAY;AACnB,cAAA,IAAI;AACF,gBAAA,MAAM,MAAA,CAAO,iBAAA,CAAkB,CAAA,CAAE,UAAA,EAAY,EAAE,QAAQ,CAAA;AACvD,gBAAA,MAAM,WAAA,CAAY,iBAAA,CAAkB,EAAE,QAAA,EAAU,KAAK,CAAA;AAAA,cACvD,CAAA,CAAA,MAAQ;AACN,gBAAA,KAAA,CAAM,MAAM,8BAA8B,CAAA;AAAA,cAC5C;AAAA,YACF,CAAA;AAAA,YACD,QAAA,EAAA;AAAA;AAAA,SAED,EACF;AAAA,OAAA,EAAA,EArBa,CAAA,EAAG,EAAE,UAAU,CAAA,CAAA,EAAI,EAAE,QAAQ,CAAA,CAsB5C,CACD,CAAA,EACH;AAAA,KAAA,EACF,CAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;AAOO,SAAS,qBAAqB,EAAE,QAAA,GAAW,IAAA,EAAM,KAAA,GAAQ,eAAc,EAAiD;AAC7H,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,uBAAA,EAAwB;AAC3C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,SAAA,CAAU,MAAM,MAAM,UAAA,CAAW,KAAK,CAAA,EAAG,EAAE,CAAA;AAC3C,EAAA,uBACE,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,aAAA,EAAY,wBAAA;AAAA,MACZ,QAAA,EAAU,IAAA;AAAA,MACV,SAAS,YAAY;AACnB,QAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,QAAA,IAAI;AACF,UAAA,MAAM,MAAA,CAAO,SAAS,QAAQ,CAAA;AAC9B,UAAA,KAAA,CAAM,QAAQ,wDAAwD,CAAA;AAAA,QACxE,CAAA,CAAA,MAAQ;AACN,UAAA,KAAA,CAAM,MAAM,0CAA0C,CAAA;AAAA,QACxD,CAAA,SAAE;AACA,UAAA,IAAI,OAAA,UAAiB,KAAK,CAAA;AAAA,QAC5B;AAAA,MACF,CAAA;AAAA,MAEC,QAAA,EAAA;AAAA;AAAA,GACH;AAEJ","file":"web.js","sourcesContent":["/**\n * `@marktiderman/genesis-notifications/web`: the notifier's web screens,\n * composed from genesis-ui only. Each reads the hooks from\n * `@marktiderman/genesis-notifications/react` (inside `NotificationsProvider`).\n *\n * NotificationBell a bell with the unread count; opens the latest few\n * NotificationFeed the whole feed, with mark read, archive and mark all read\n * PreferencesMatrix categories × channels, a switch each (critical rows locked on)\n * DeliveryLog admin: what was sent, to whom (masked), and what became of it\n * SuppressionList admin: addresses not to email, and lifting one\n * SendTestNotification admin: send yourself a test on every channel\n *\n * @packageDocumentation\n */\nimport { useEffect, useState, type ReactNode } from \"react\";\nimport { Bell } from \"lucide-react\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmptyState,\n Popover,\n PopoverContent,\n PopoverTrigger,\n ScrollArea,\n Separator,\n Switch,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n cn,\n toast,\n} from \"@marktiderman/genesis-ui\";\nimport type { Channel } from \"../core/index\";\nimport { useNotifications, useNotificationsContext, usePreferences, useUnreadCount, type FeedItem } from \"../react/index\";\n\nexport const CHANNEL_LABELS: Record<Channel, string> = { in_app: \"In the app\", push: \"Push\", email: \"Email\" };\n\nfunction relative(iso: string, now = Date.now()): string {\n const s = Math.max(0, Math.round((now - Date.parse(iso)) / 1000));\n if (s < 60) return \"just now\";\n if (s < 3600) return `${Math.floor(s / 60)}m`;\n if (s < 86400) return `${Math.floor(s / 3600)}h`;\n return new Date(iso).toLocaleDateString();\n}\n\nexport interface FeedRowProps {\n item: FeedItem;\n onOpen?: (item: FeedItem) => void;\n testID?: string;\n}\n\n/** One feed row: unread dot, title, body, age. */\nexport function NotificationRowView({ item, onOpen, testID }: FeedRowProps) {\n return (\n <Button\n variant=\"ghost\"\n data-testid={testID ?? `notification-${item.id}`}\n onClick={() => onOpen?.(item)}\n className={cn(\"h-auto w-full items-start justify-start gap-3 whitespace-normal px-3 py-2 text-left font-normal\", item.unread && \"bg-muted/40\")}\n >\n <span aria-hidden className={cn(\"mt-1.5 h-2 w-2 shrink-0 rounded-full\", item.unread ? \"bg-primary\" : \"bg-transparent\")} />\n <span className=\"min-w-0 flex-1\">\n <span className={cn(\"block truncate text-sm\", item.unread ? \"font-semibold\" : \"font-medium\")}>{item.title}</span>\n {item.body ? <span className=\"block truncate text-sm text-muted-foreground\">{item.body}</span> : null}\n </span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{relative(item.created_at)}</span>\n {item.unread ? <span className=\"sr-only\">Unread</span> : null}\n </Button>\n );\n}\n\nexport interface NotificationBellProps {\n /** Opens a notification's link (your router's navigate). Marked read first. */\n onOpen?: (item: FeedItem) => void;\n /** Where \"See all\" goes. */\n onSeeAll?: () => void;\n /** How many to show. Default 8. */\n limit?: number;\n className?: string;\n}\n\n/**\n * The bell. Opening it marks what it shows as seen; opening a row marks it read.\n *\n * @stability Beta\n */\nexport function NotificationBell({ onOpen, onSeeAll, limit = 8, className }: NotificationBellProps) {\n const unread = useUnreadCount();\n const { items, markRead, markAllRead, markAllSeen } = useNotifications({ limit });\n const label = unread > 0 ? `Notifications, ${unread} unread` : \"Notifications\";\n return (\n <Popover onOpenChange={(open) => (open && items.some((i) => !i.seen_at) ? void markAllSeen() : undefined)}>\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" aria-label={label} data-testid=\"notification-bell\" className={cn(\"relative\", className)}>\n <Bell aria-hidden className=\"h-5 w-5\" />\n {unread > 0 ? (\n <Badge data-testid=\"notification-bell-count\" className=\"absolute -right-1 -top-1 h-5 min-w-5 justify-center px-1 text-xs\">\n {unread > 99 ? \"99+\" : unread}\n </Badge>\n ) : null}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-80 p-0\">\n <div className=\"flex items-center justify-between px-3 py-2\">\n <span className=\"text-sm font-semibold\">Notifications</span>\n {unread > 0 ? (\n <Button variant=\"link\" size=\"sm\" className=\"h-auto p-0\" data-testid=\"notification-bell-mark-all\" onClick={() => void markAllRead()}>\n Mark all read\n </Button>\n ) : null}\n </div>\n <Separator />\n {items.length === 0 ? (\n <EmptyState title=\"No notifications\" description=\"When you get one, it'll show up here.\" className=\"py-8\" />\n ) : (\n <ScrollArea className=\"max-h-96\">\n <div className=\"p-1\">\n {items.map((item) => (\n <NotificationRowView\n key={item.id}\n item={item}\n onOpen={(i) => {\n if (i.unread) void markRead(i.id);\n onOpen?.(i);\n }}\n />\n ))}\n </div>\n </ScrollArea>\n )}\n {onSeeAll ? (\n <>\n <Separator />\n <Button variant=\"ghost\" className=\"w-full rounded-t-none\" data-testid=\"notification-bell-see-all\" onClick={onSeeAll}>\n See all\n </Button>\n </>\n ) : null}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface NotificationFeedProps {\n onOpen?: (item: FeedItem) => void;\n limit?: number;\n title?: ReactNode;\n}\n\n/**\n * The whole feed, in a card: mark read on open, archive, mark all read.\n *\n * @stability Beta\n */\nexport function NotificationFeed({ onOpen, limit = 100, title = \"Notifications\" }: NotificationFeedProps) {\n const { items, isLoading, error, markRead, markAllRead, archive } = useNotifications({ limit });\n const anyUnread = items.some((i) => i.unread);\n return (\n <Card data-testid=\"notification-feed\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle>{title}</CardTitle>\n {anyUnread ? (\n <Button variant=\"outline\" size=\"sm\" data-testid=\"notification-feed-mark-all\" onClick={() => void markAllRead()}>\n Mark all read\n </Button>\n ) : null}\n </CardHeader>\n <CardContent className=\"p-2\">\n {error ? (\n <EmptyState title=\"Couldn't load notifications\" description=\"Check your connection and try again.\" />\n ) : isLoading ? (\n <p className=\"p-4 text-sm text-muted-foreground\">Loading…</p>\n ) : items.length === 0 ? (\n <EmptyState title=\"No notifications\" description=\"When you get one, it'll show up here.\" />\n ) : (\n <ul className=\"divide-y divide-border\">\n {items.map((item) => (\n <li key={item.id} className=\"flex items-center gap-2\">\n <NotificationRowView\n item={item}\n onOpen={(i) => {\n if (i.unread) void markRead(i.id);\n onOpen?.(i);\n }}\n />\n <Button variant=\"ghost\" size=\"sm\" aria-label={`Archive ${item.title}`} data-testid={`notification-archive-${item.id}`} onClick={() => void archive(item.id)}>\n Archive\n </Button>\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n );\n}\n\n/**\n * Categories × channels, a switch each. Critical categories are locked on.\n * Saves each change at once and puts it back if the save fails.\n *\n * @stability Beta\n */\nexport function PreferencesMatrix({ channelLabels = CHANNEL_LABELS }: { channelLabels?: Partial<Record<Channel, string>> }) {\n const { rows, status, set } = usePreferences();\n const columns = ([\"in_app\", \"push\", \"email\"] as const).filter((ch) => rows.some((r) => r.channels.includes(ch)));\n if (status === \"error\") return <EmptyState title=\"Couldn't load your notification settings\" description=\"Check your connection and try again.\" />;\n return (\n <Table data-testid=\"notification-preferences\">\n <TableHeader>\n <TableRow>\n <TableHead>Notify me about</TableHead>\n {columns.map((ch) => (\n <TableHead key={ch} className=\"text-center\">\n {channelLabels[ch] ?? CHANNEL_LABELS[ch]}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((row) => (\n <TableRow key={row.id}>\n <TableCell>\n <div className=\"font-medium\">{row.label}</div>\n {row.description ? <div className=\"text-sm text-muted-foreground\">{row.description}</div> : null}\n {row.critical ? <div className=\"text-xs text-muted-foreground\">Always on</div> : null}\n </TableCell>\n {columns.map((ch) => (\n <TableCell key={ch} className=\"text-center\">\n {row.channels.includes(ch) ? (\n <Switch\n aria-label={`${row.label}: ${channelLabels[ch] ?? CHANNEL_LABELS[ch]}`}\n data-testid={`notification-preference-${row.id}-${ch}`}\n checked={Boolean(row.values[ch])}\n disabled={status !== \"ready\" || row.critical}\n onCheckedChange={(on) =>\n void set(row.id, ch, on).catch(() => toast.error(\"Couldn't save that change. Check your connection and try again.\"))\n }\n />\n ) : (\n <span className=\"text-muted-foreground\" aria-label=\"Not available\">\n –\n </span>\n )}\n </TableCell>\n ))}\n </TableRow>\n ))}\n </TableBody>\n </Table>\n );\n}\n\nconst STATUS_VARIANT: Record<string, \"success\" | \"warning\" | \"destructive\" | \"muted\" | \"info\"> = {\n delivered: \"success\",\n sent: \"success\",\n pending: \"info\",\n sending: \"info\",\n failed: \"destructive\",\n skipped: \"muted\",\n cancelled: \"muted\",\n};\n\n/**\n * Admin: the delivery log, newest first. Row level security shows an admin\n * their tenants' rows only; content and full addresses are never here.\n *\n * @stability Beta\n */\nexport function DeliveryLog({ tenantId, limit = 100 }: { tenantId?: string | null; limit?: number }) {\n const { client, userId } = useNotificationsContext();\n const query = useQuery({ queryKey: [\"genesis-notifications\", userId, \"log\", tenantId ?? \"all\", limit], queryFn: () => client.deliveryLog({ tenantId, limit }), enabled: userId !== null });\n const rows = query.data ?? [];\n return (\n <Card data-testid=\"notification-delivery-log\">\n <CardHeader>\n <CardTitle>Delivery log</CardTitle>\n <CardDescription>Every push and email the app sent, and what became of it.</CardDescription>\n </CardHeader>\n <CardContent>\n {query.isError ? (\n <EmptyState title=\"Couldn't load the log\" description=\"You may not be an admin of this workspace.\" />\n ) : rows.length === 0 ? (\n <EmptyState title=\"Nothing sent yet\" />\n ) : (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>When</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Channel</TableHead>\n <TableHead>To</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Detail</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((r) => (\n <TableRow key={r.id} data-testid={`notification-delivery-${r.id}`}>\n <TableCell className=\"whitespace-nowrap text-sm\">{new Date(r.created_at).toLocaleString()}</TableCell>\n <TableCell className=\"font-mono text-xs\">{r.type}</TableCell>\n <TableCell>{r.channel === \"email\" ? \"Email\" : \"Push\"}</TableCell>\n <TableCell className=\"text-sm\">{r.recipient_masked ?? \"—\"}</TableCell>\n <TableCell>\n <Badge variant={STATUS_VARIANT[r.status] ?? \"muted\"}>{r.status}</Badge>\n </TableCell>\n <TableCell className=\"text-sm text-muted-foreground\">\n {r.skip_reason ?? r.error ?? (r.attempts > 1 ? `${r.attempts} attempts` : \"\")}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n );\n}\n\nconst REASONS: Record<string, string> = { bounce: \"Bounced\", complaint: \"Marked as spam\", unsubscribe: \"Unsubscribed\", manual: \"Added by an admin\" };\n\n/**\n * Admin: addresses the app won't email (by a masked address; the list keeps\n * only a hash), and lifting one.\n *\n * @stability Beta\n */\nexport function SuppressionList({ limit = 200 }: { limit?: number }) {\n const { client, userId } = useNotificationsContext();\n const queryClient = useQueryClient();\n const key = [\"genesis-notifications\", userId, \"suppressions\", limit];\n const query = useQuery({ queryKey: key, queryFn: () => client.suppressions({ limit }), enabled: userId !== null });\n const rows = query.data ?? [];\n return (\n <Card data-testid=\"notification-suppressions\">\n <CardHeader>\n <CardTitle>Suppressed addresses</CardTitle>\n <CardDescription>Bounces and spam complaints stop email to an address; lift one once it's fixed.</CardDescription>\n </CardHeader>\n <CardContent>\n {query.isError ? (\n <EmptyState title=\"Couldn't load the list\" description=\"Only the app's admins can see it.\" />\n ) : rows.length === 0 ? (\n <EmptyState title=\"No suppressed addresses\" />\n ) : (\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Address</TableHead>\n <TableHead>Why</TableHead>\n <TableHead>Category</TableHead>\n <TableHead>Since</TableHead>\n <TableHead />\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((r) => (\n <TableRow key={`${r.email_hash}:${r.category}`}>\n <TableCell>{r.email_masked ?? `${r.email_hash.slice(0, 8)}…`}</TableCell>\n <TableCell>{REASONS[r.reason] ?? r.reason}</TableCell>\n <TableCell>{r.category === \"*\" ? \"All\" : r.category}</TableCell>\n <TableCell className=\"whitespace-nowrap text-sm\">{new Date(r.created_at).toLocaleDateString()}</TableCell>\n <TableCell className=\"text-right\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n data-testid={`notification-suppression-lift-${r.email_hash.slice(0, 12)}`}\n onClick={async () => {\n try {\n await client.removeSuppression(r.email_hash, r.category);\n await queryClient.invalidateQueries({ queryKey: key });\n } catch {\n toast.error(\"Couldn't lift it. Try again.\");\n }\n }}\n >\n Lift\n </Button>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n );\n}\n\n/**\n * Admin: send yourself a test notification (in the app, push and email).\n *\n * @stability Beta\n */\nexport function SendTestNotification({ tenantId = null, label = \"Send a test\" }: { tenantId?: string | null; label?: string }) {\n const { client } = useNotificationsContext();\n const [busy, setBusy] = useState(false);\n const [mounted, setMounted] = useState(true);\n useEffect(() => () => setMounted(false), []);\n return (\n <Button\n data-testid=\"notification-send-test\"\n disabled={busy}\n onClick={async () => {\n setBusy(true);\n try {\n await client.sendTest(tenantId);\n toast.success(\"Test sent. It reaches you on each channel you have on.\");\n } catch {\n toast.error(\"Couldn't send the test. Only admins can.\");\n } finally {\n if (mounted) setBusy(false);\n }\n }}\n >\n {label}\n </Button>\n );\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// genesis-notifications: the notifier's sender, as a Supabase Edge Function.
|
|
2
|
+
// A template from @marktiderman/genesis-notifications: copy this folder to your
|
|
3
|
+
// app's `supabase/functions/`, put your registry (and email templates) in
|
|
4
|
+
// `notifications.ts` beside it, and deploy with `--no-verify-jwt` (it checks
|
|
5
|
+
// its own secrets).
|
|
6
|
+
//
|
|
7
|
+
// POST /functions/v1/genesis-notifications/run every minute
|
|
8
|
+
// POST /functions/v1/genesis-notifications/receipts every 15 minutes
|
|
9
|
+
// (both with Authorization: Bearer <GENESIS_NOTIFICATIONS_CRON_SECRET>, from pg_cron + pg_net)
|
|
10
|
+
// POST /functions/v1/genesis-notifications/webhooks/resend Resend's webhook (bounces, complaints)
|
|
11
|
+
// GET|POST /functions/v1/genesis-notifications/unsubscribe the one-click unsubscribe link
|
|
12
|
+
//
|
|
13
|
+
// Secrets (`supabase secrets set …`): GENESIS_NOTIFICATIONS_CRON_SECRET and
|
|
14
|
+
// GENESIS_NOTIFICATIONS_UNSUBSCRIBE_SECRET (32 characters or more each),
|
|
15
|
+
// RESEND_API_KEY, RESEND_WEBHOOK_SECRET, EXPO_ACCESS_TOKEN, and
|
|
16
|
+
// NOTIFICATIONS_FROM_DOMAIN / NOTIFICATIONS_UPDATES_DOMAIN / APP_NAME / APP_URL.
|
|
17
|
+
// SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are set by Supabase.
|
|
18
|
+
import { createClient } from "npm:@supabase/supabase-js@2.118.0";
|
|
19
|
+
import {
|
|
20
|
+
createNotificationRunner,
|
|
21
|
+
createNotificationsHandler,
|
|
22
|
+
expoPushProvider,
|
|
23
|
+
resendProvider,
|
|
24
|
+
supabaseNotificationStore,
|
|
25
|
+
} from "npm:@marktiderman/genesis-notifications@^0.1.0/server";
|
|
26
|
+
import { notifications, emailTemplates, secretLinks } from "./notifications.ts";
|
|
27
|
+
|
|
28
|
+
const env = (name: string) => Deno.env.get(name) ?? "";
|
|
29
|
+
const admin = createClient(env("SUPABASE_URL"), env("SUPABASE_SERVICE_ROLE_KEY"), { auth: { persistSession: false, autoRefreshToken: false } });
|
|
30
|
+
const store = supabaseNotificationStore(admin);
|
|
31
|
+
const functionUrl = `${env("SUPABASE_URL")}/functions/v1/genesis-notifications`;
|
|
32
|
+
|
|
33
|
+
const runner = createNotificationRunner({
|
|
34
|
+
store,
|
|
35
|
+
registry: notifications,
|
|
36
|
+
push: { provider: expoPushProvider({ accessToken: env("EXPO_ACCESS_TOKEN") || undefined }) },
|
|
37
|
+
email: env("RESEND_API_KEY")
|
|
38
|
+
? {
|
|
39
|
+
provider: resendProvider({ apiKey: env("RESEND_API_KEY") }),
|
|
40
|
+
appName: env("APP_NAME") || "Notifications",
|
|
41
|
+
domains: { transactional: env("NOTIFICATIONS_FROM_DOMAIN"), other: env("NOTIFICATIONS_UPDATES_DOMAIN") || undefined },
|
|
42
|
+
templates: emailTemplates,
|
|
43
|
+
secretLinks,
|
|
44
|
+
unsubscribe: { url: `${functionUrl}/unsubscribe`, secret: env("GENESIS_NOTIFICATIONS_UNSUBSCRIBE_SECRET") },
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
appUrl: env("APP_URL") || undefined,
|
|
48
|
+
log: (message) => console.error(message),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Deno.serve(
|
|
52
|
+
createNotificationsHandler({
|
|
53
|
+
runner,
|
|
54
|
+
store,
|
|
55
|
+
cronSecret: env("GENESIS_NOTIFICATIONS_CRON_SECRET"),
|
|
56
|
+
unsubscribeSecret: env("GENESIS_NOTIFICATIONS_UNSUBSCRIBE_SECRET") || undefined,
|
|
57
|
+
resendWebhookSecret: env("RESEND_WEBHOOK_SECRET") || undefined,
|
|
58
|
+
log: (message) => console.error(message),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Your registry, email templates and secret links, for the sender function.
|
|
2
|
+
// Import the same `defineNotifications` list your app uses, e.g.
|
|
3
|
+
// export { notifications } from "../../../src/notifications.ts";
|
|
4
|
+
// This starter list has the one type every app needs: the admin's test,
|
|
5
|
+
// spread from the package so it stays critical (as the migration seeds it) and
|
|
6
|
+
// a preference can never silence Admin -> Send a test.
|
|
7
|
+
import { GENESIS_TEST_CATEGORY, GENESIS_TEST_TYPE, defineNotifications } from "npm:@marktiderman/genesis-notifications@^0.1.0";
|
|
8
|
+
import type { EmailTemplate, SecretLinks } from "npm:@marktiderman/genesis-notifications@^0.1.0/server";
|
|
9
|
+
|
|
10
|
+
export const notifications = defineNotifications({
|
|
11
|
+
categories: { ...GENESIS_TEST_CATEGORY },
|
|
12
|
+
types: { ...GENESIS_TEST_TYPE },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const emailTemplates: Record<string, EmailTemplate> = {};
|
|
16
|
+
// A secret link builder must give the same link for the same notification every time:
|
|
17
|
+
// a retry sends under the same idempotency key, and a different body fails (409).
|
|
18
|
+
export const secretLinks: SecretLinks = {};
|