@hook-sdk/template 0.1.4 → 0.2.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/dist/index.cjs CHANGED
@@ -29,12 +29,14 @@ __export(index_exports, {
29
29
  EmptyState: () => EmptyState,
30
30
  ErrorBoundary: () => ErrorBoundary,
31
31
  LoadingState: () => LoadingState,
32
+ PushPrompt: () => PushPrompt2,
32
33
  useAuth: () => useAuth,
33
34
  useAuthPrimitives: () => useAuthPrimitives,
34
35
  useForgotForm: () => useForgotForm,
35
36
  useLoginForm: () => useLoginForm,
36
37
  usePaywallState: () => usePaywallState,
37
38
  usePush: () => usePush,
39
+ useReminders: () => useReminders,
38
40
  useResetForm: () => useResetForm,
39
41
  useSignupForm: () => useSignupForm,
40
42
  useSubscription: () => useSubscription,
@@ -821,23 +823,123 @@ function AppRoot({
821
823
  ] }) }) }) }) }) }) });
822
824
  }
823
825
 
824
- // src/defaults/EmptyState.tsx
826
+ // src/hooks/usePush.ts
827
+ var import_react12 = require("react");
828
+ var import_sdk10 = require("@hook-sdk/sdk");
829
+ function detectIosNeedsInstall() {
830
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
831
+ const ua = navigator.userAgent || "";
832
+ const isIos = /iPhone|iPad|iPod/.test(ua);
833
+ if (!isIos) return false;
834
+ const mm = window.matchMedia?.("(display-mode: standalone)");
835
+ const standalone = mm?.matches === true;
836
+ const legacyStandalone = typeof navigator.standalone === "boolean" ? navigator.standalone : false;
837
+ return !(standalone || legacyStandalone);
838
+ }
839
+ function deriveState(push) {
840
+ if (!push.isAvailable()) {
841
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
842
+ return { kind: "unsupported" };
843
+ }
844
+ const status = push.status();
845
+ if (status === "granted") return { kind: "subscribed" };
846
+ if (status === "denied") return { kind: "denied" };
847
+ if (status === "unsupported") {
848
+ if (detectIosNeedsInstall()) return { kind: "ios_needs_install" };
849
+ return { kind: "unsupported" };
850
+ }
851
+ return { kind: "prompt" };
852
+ }
853
+ function usePush() {
854
+ const { push } = (0, import_sdk10.useHook)();
855
+ const [state, setState] = (0, import_react12.useState)(() => deriveState(push));
856
+ (0, import_react12.useEffect)(() => {
857
+ setState(deriveState(push));
858
+ }, [push]);
859
+ const subscribe = (0, import_react12.useCallback)(async () => {
860
+ try {
861
+ await push.subscribe();
862
+ setState({ kind: "subscribed" });
863
+ } catch (e) {
864
+ const code = e?.code ?? "push.unknown";
865
+ const message = e?.message ?? "Push subscription failed";
866
+ if (code === "push.permission_denied") setState({ kind: "denied" });
867
+ else setState({ kind: "error", code, message });
868
+ throw e;
869
+ }
870
+ }, [push]);
871
+ const unsubscribe = (0, import_react12.useCallback)(async () => {
872
+ try {
873
+ await push.unsubscribe();
874
+ setState({ kind: "prompt" });
875
+ } catch (e) {
876
+ setState({ kind: "error", code: e?.code ?? "push.unknown", message: e?.message ?? "failed" });
877
+ throw e;
878
+ }
879
+ }, [push]);
880
+ return { state, subscribe, unsubscribe };
881
+ }
882
+
883
+ // src/components/PushPrompt.tsx
825
884
  var import_jsx_runtime14 = require("react/jsx-runtime");
885
+ function PushPrompt2({ texts, onSubscribed, onDeclined, onInstallRequested, className }) {
886
+ const { state, subscribe } = usePush();
887
+ if (state.kind === "subscribed") return null;
888
+ if (state.kind === "ios_needs_install") {
889
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className, role: "region", "aria-label": texts.iosInstallTitle, children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { children: texts.iosInstallTitle }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: texts.iosInstallBody }),
892
+ onInstallRequested && texts.iosInstallCta && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { onClick: onInstallRequested, children: texts.iosInstallCta })
893
+ ] });
894
+ }
895
+ if (state.kind === "denied") {
896
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className, role: "region", "aria-label": texts.deniedTitle, children: [
897
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { children: texts.deniedTitle }),
898
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: texts.deniedBody })
899
+ ] });
900
+ }
901
+ if (state.kind === "unsupported") {
902
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className, role: "region", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: texts.unsupportedBody }) });
903
+ }
904
+ if (state.kind === "error") {
905
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className, role: "region", "aria-label": "error", children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { children: state.message }) });
906
+ }
907
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className, role: "region", children: [
908
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
909
+ "button",
910
+ {
911
+ type: "button",
912
+ onClick: async () => {
913
+ try {
914
+ await subscribe();
915
+ onSubscribed?.();
916
+ } catch {
917
+ }
918
+ },
919
+ children: texts.cta
920
+ }
921
+ ),
922
+ onDeclined && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", onClick: onDeclined, children: texts.declineCta })
923
+ ] });
924
+ }
925
+
926
+ // src/defaults/EmptyState.tsx
927
+ var import_jsx_runtime15 = require("react/jsx-runtime");
826
928
  function EmptyState({ title, description, action }) {
827
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
828
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
829
- description && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: { opacity: 0.7 }, children: description }),
830
- action && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { style: { marginTop: 16 }, children: action })
929
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { role: "status", style: { padding: 32, textAlign: "center" }, children: [
930
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h2", { style: { marginBottom: 8 }, children: title }),
931
+ description && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { opacity: 0.7 }, children: description }),
932
+ action && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { marginTop: 16 }, children: action })
831
933
  ] });
832
934
  }
833
935
 
834
936
  // src/hooks/useAuthPrimitives.ts
835
- var import_react12 = require("react");
836
- var import_sdk10 = require("@hook-sdk/sdk");
937
+ var import_react13 = require("react");
938
+ var import_sdk11 = require("@hook-sdk/sdk");
837
939
  var warned = false;
838
940
  function useAuthPrimitives() {
839
- const { auth } = (0, import_sdk10.useHook)();
840
- (0, import_react12.useEffect)(() => {
941
+ const { auth } = (0, import_sdk11.useHook)();
942
+ (0, import_react13.useEffect)(() => {
841
943
  if (!warned && process.env.NODE_ENV !== "production") {
842
944
  warned = true;
843
945
  console.warn(
@@ -859,37 +961,63 @@ function useAuthPrimitives() {
859
961
  }
860
962
 
861
963
  // src/hooks/useSubscription.ts
862
- var import_sdk11 = require("@hook-sdk/sdk");
964
+ var import_sdk12 = require("@hook-sdk/sdk");
863
965
  function useSubscription() {
864
- const { subscription } = (0, import_sdk11.useHook)();
966
+ const { subscription } = (0, import_sdk12.useHook)();
865
967
  return {
866
968
  status: subscription.status()
867
969
  };
868
970
  }
869
971
 
870
- // src/hooks/usePush.ts
871
- var import_sdk12 = require("@hook-sdk/sdk");
872
- function usePush() {
873
- const { push } = (0, import_sdk12.useHook)();
874
- return {
875
- status: push.status(),
876
- subscribe: push.subscribe,
877
- unsubscribe: push.unsubscribe
878
- };
972
+ // src/hooks/useReminders.ts
973
+ var import_react14 = require("react");
974
+ var import_sdk13 = require("@hook-sdk/sdk");
975
+ function useReminders() {
976
+ const { push } = (0, import_sdk13.useHook)();
977
+ const r = push.reminders;
978
+ const [reminders, setReminders] = (0, import_react14.useState)([]);
979
+ const [loading, setLoading] = (0, import_react14.useState)(true);
980
+ const reload = (0, import_react14.useCallback)(async () => {
981
+ setLoading(true);
982
+ try {
983
+ const next = await r.list();
984
+ setReminders(next);
985
+ } finally {
986
+ setLoading(false);
987
+ }
988
+ }, [r]);
989
+ (0, import_react14.useEffect)(() => {
990
+ void reload();
991
+ }, [reload]);
992
+ const setReminder = (0, import_react14.useCallback)(async (input) => {
993
+ await r.set(input);
994
+ await reload();
995
+ }, [r, reload]);
996
+ const deleteReminder = (0, import_react14.useCallback)(async (slot) => {
997
+ await r.delete(slot);
998
+ await reload();
999
+ }, [r, reload]);
1000
+ const schedule = (0, import_react14.useCallback)(async (items) => {
1001
+ return r.schedule(items);
1002
+ }, [r]);
1003
+ const setFallbacks = (0, import_react14.useCallback)(async (items) => {
1004
+ return r.setFallbacks(items);
1005
+ }, [r]);
1006
+ return { reminders, loading, setReminder, deleteReminder, schedule, setFallbacks };
879
1007
  }
880
1008
 
881
1009
  // src/hooks/useToast.ts
882
- var import_react13 = require("react");
1010
+ var import_react15 = require("react");
883
1011
  function useToast() {
884
- const [items, setItems] = (0, import_react13.useState)([]);
885
- const show = (0, import_react13.useCallback)((message, kind = "info") => {
1012
+ const [items, setItems] = (0, import_react15.useState)([]);
1013
+ const show = (0, import_react15.useCallback)((message, kind = "info") => {
886
1014
  const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
887
1015
  setItems((prev) => [...prev, { id, message, kind }]);
888
1016
  setTimeout(() => {
889
1017
  setItems((prev) => prev.filter((t) => t.id !== id));
890
1018
  }, 4e3);
891
1019
  }, []);
892
- const dismiss = (0, import_react13.useCallback)((id) => {
1020
+ const dismiss = (0, import_react15.useCallback)((id) => {
893
1021
  setItems((prev) => prev.filter((t) => t.id !== id));
894
1022
  }, []);
895
1023
  return { items, show, dismiss };
@@ -905,12 +1033,14 @@ function useToast() {
905
1033
  EmptyState,
906
1034
  ErrorBoundary,
907
1035
  LoadingState,
1036
+ PushPrompt,
908
1037
  useAuth,
909
1038
  useAuthPrimitives,
910
1039
  useForgotForm,
911
1040
  useLoginForm,
912
1041
  usePaywallState,
913
1042
  usePush,
1043
+ useReminders,
914
1044
  useResetForm,
915
1045
  useSignupForm,
916
1046
  useSubscription,