@saastro/forms 0.2.0 → 0.3.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.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  useHasComponentProvider,
12
12
  usePartialComponents,
13
13
  withComponents
14
- } from "./chunk-GHDCNAWC.js";
14
+ } from "./chunk-G2ZSBYVS.js";
15
15
  import {
16
16
  applyBuiltinTransform,
17
17
  applyFieldMapping,
@@ -1362,7 +1362,7 @@ function renderSwitchGroupField(fieldConfig, ctx) {
1362
1362
  import { Suspense, lazy } from "react";
1363
1363
  import { jsx as jsx7 } from "react/jsx-runtime";
1364
1364
  var DateRenderersLazy = lazy(
1365
- () => import("./DateRenderers-3JUQNLKJ.js").then((m) => ({ default: m.DateRenderers }))
1365
+ () => import("./DateRenderers-QKRBXFC6.js").then((m) => ({ default: m.DateRenderers }))
1366
1366
  );
1367
1367
  function renderDateField(fieldConfig, ctx, control, name) {
1368
1368
  return /* @__PURE__ */ jsx7(
@@ -6799,6 +6799,165 @@ var BUILTIN_RESOLVERS = [
6799
6799
  { id: "urlParam", label: "URL Parameter", description: "Value from a URL query parameter" }
6800
6800
  ];
6801
6801
 
6802
+ // src/templates/contact.ts
6803
+ function createContactTemplate() {
6804
+ return FormBuilder.create("contact").layout("auto").columns(1).gap(4).addField(
6805
+ "name",
6806
+ (f) => f.type("text").label("Name").placeholder("Your full name").required("Please enter your name").minLength(2, "Name must be at least 2 characters")
6807
+ ).addField(
6808
+ "email",
6809
+ (f) => f.type("email").label("Email").placeholder("you@example.com").required("Please enter your email").email("Please enter a valid email address")
6810
+ ).addField(
6811
+ "message",
6812
+ (f) => f.type("textarea").label("Message").placeholder("How can we help?").required("Please write a short message").minLength(10, "Message must be at least 10 characters")
6813
+ ).addStep("main", ["name", "email", "message"]).initialStep("main").buttons({
6814
+ submit: { type: "submit", label: "Send message" },
6815
+ align: "end"
6816
+ }).build();
6817
+ }
6818
+
6819
+ // src/templates/newsletter.ts
6820
+ function createNewsletterTemplate() {
6821
+ return FormBuilder.create("newsletter").layout("auto").columns(1).gap(4).addField(
6822
+ "email",
6823
+ (f) => f.type("email").label("Email").placeholder("you@example.com").required("Please enter your email").email("Please enter a valid email address")
6824
+ ).addField(
6825
+ "consent",
6826
+ (f) => f.type("checkbox").label("I agree to receive newsletter emails").required("You must agree to receive the newsletter")
6827
+ ).addStep("main", ["email", "consent"]).initialStep("main").buttons({
6828
+ submit: { type: "submit", label: "Subscribe" },
6829
+ align: "end"
6830
+ }).build();
6831
+ }
6832
+
6833
+ // src/templates/lead-gen.ts
6834
+ function createLeadGenTemplate() {
6835
+ return FormBuilder.create("lead-gen").layout("auto").columns(2).gap(4).addField(
6836
+ "name",
6837
+ (f) => f.type("text").label("Full name").placeholder("Jane Doe").required("Please enter your name").minLength(2, "Name must be at least 2 characters").columns({ default: 1 })
6838
+ ).addField(
6839
+ "email",
6840
+ (f) => f.type("email").label("Work email").placeholder("jane@acme.com").required("Please enter your email").email("Please enter a valid email address").columns({ default: 1 })
6841
+ ).addField(
6842
+ "phone",
6843
+ (f) => f.type("tel").label("Phone").placeholder("+1 555 0123").optional().columns({ default: 1 })
6844
+ ).addField(
6845
+ "company",
6846
+ (f) => f.type("text").label("Company").placeholder("Acme Inc.").optional().columns({ default: 1 })
6847
+ ).addField(
6848
+ "message",
6849
+ (f) => f.type("textarea").label("What are you looking to solve?").placeholder("A few words about your project or use case").required("Please describe your need briefly").minLength(10, "Message must be at least 10 characters").columns({ default: 2 })
6850
+ ).addStep("main", ["name", "email", "phone", "company", "message"]).initialStep("main").buttons({
6851
+ submit: { type: "submit", label: "Request a call" },
6852
+ align: "end"
6853
+ }).build();
6854
+ }
6855
+
6856
+ // src/templates/event-rsvp.ts
6857
+ function createEventRsvpTemplate() {
6858
+ return FormBuilder.create("event-rsvp").layout("auto").columns(2).gap(4).addField(
6859
+ "name",
6860
+ (f) => f.type("text").label("Full name").placeholder("Jane Doe").required("Please enter your name").minLength(2, "Name must be at least 2 characters").columns({ default: 1 })
6861
+ ).addField(
6862
+ "email",
6863
+ (f) => f.type("email").label("Email").placeholder("jane@example.com").required("Please enter your email").email("Please enter a valid email address").columns({ default: 1 })
6864
+ ).addField(
6865
+ "attending",
6866
+ (f) => f.type("radio").label("Will you attend?").options([
6867
+ { label: "Yes, I will be there", value: "yes" },
6868
+ { label: "No, I cannot make it", value: "no" },
6869
+ { label: "Not sure yet", value: "maybe" }
6870
+ ]).required("Please pick one").columns({ default: 2 })
6871
+ ).addField(
6872
+ "guests",
6873
+ (f) => f.type("number").label("Number of guests (including yourself)").min(1).max(10).optional().columns({ default: 1 })
6874
+ ).addField(
6875
+ "dietary",
6876
+ (f) => f.type("textarea").label("Dietary notes").placeholder("Allergies, preferences, etc.").optional().columns({ default: 2 })
6877
+ ).addStep("main", ["name", "email", "attending", "guests", "dietary"]).initialStep("main").buttons({
6878
+ submit: { type: "submit", label: "Send RSVP" },
6879
+ align: "end"
6880
+ }).build();
6881
+ }
6882
+
6883
+ // src/templates/quote-request.ts
6884
+ function createQuoteRequestTemplate() {
6885
+ return FormBuilder.create("quote-request").layout("auto").columns(2).gap(4).addField(
6886
+ "name",
6887
+ (f) => f.type("text").label("Full name").placeholder("Jane Doe").required("Please enter your name").minLength(2, "Name must be at least 2 characters").columns({ default: 1 })
6888
+ ).addField(
6889
+ "email",
6890
+ (f) => f.type("email").label("Work email").placeholder("jane@acme.com").required("Please enter your email").email("Please enter a valid email address").columns({ default: 1 })
6891
+ ).addField(
6892
+ "company",
6893
+ (f) => f.type("text").label("Company").placeholder("Acme Inc.").required("Please enter your company name").columns({ default: 1 })
6894
+ ).addField(
6895
+ "service",
6896
+ (f) => f.type("select").label("Service type").placeholder("Pick the closest match").options([
6897
+ { label: "Design", value: "design" },
6898
+ { label: "Development", value: "development" },
6899
+ { label: "Consulting", value: "consulting" },
6900
+ { label: "Other", value: "other" }
6901
+ ]).required("Please pick a service").columns({ default: 1 })
6902
+ ).addField(
6903
+ "budget",
6904
+ (f) => f.type("select").label("Estimated budget").placeholder("Pick a range").options([
6905
+ { label: "Under $5k", value: "under-5k" },
6906
+ { label: "$5k \u2013 $25k", value: "5k-25k" },
6907
+ { label: "$25k \u2013 $100k", value: "25k-100k" },
6908
+ { label: "$100k+", value: "100k+" },
6909
+ { label: "Not sure yet", value: "unknown" }
6910
+ ]).required("Please pick a budget range").columns({ default: 2 })
6911
+ ).addField(
6912
+ "details",
6913
+ (f) => f.type("textarea").label("Project details").placeholder("Goals, timeline, anything we should know").required("Please share a few sentences about the project").minLength(20, "Project details must be at least 20 characters").columns({ default: 2 })
6914
+ ).addStep("main", ["name", "email", "company", "service", "budget", "details"]).initialStep("main").buttons({
6915
+ submit: { type: "submit", label: "Request a quote" },
6916
+ align: "end"
6917
+ }).build();
6918
+ }
6919
+
6920
+ // src/templates/index.ts
6921
+ var TEMPLATES = {
6922
+ contact: createContactTemplate,
6923
+ newsletter: createNewsletterTemplate,
6924
+ "lead-gen": createLeadGenTemplate,
6925
+ "event-rsvp": createEventRsvpTemplate,
6926
+ "quote-request": createQuoteRequestTemplate
6927
+ };
6928
+ var TEMPLATE_META = [
6929
+ {
6930
+ id: "contact",
6931
+ label: "Contact",
6932
+ description: "Name, email, and a message. The classic three-field form.",
6933
+ fieldCount: 3
6934
+ },
6935
+ {
6936
+ id: "newsletter",
6937
+ label: "Newsletter",
6938
+ description: "Email plus a GDPR-friendly consent checkbox.",
6939
+ fieldCount: 2
6940
+ },
6941
+ {
6942
+ id: "lead-gen",
6943
+ label: "Lead generation",
6944
+ description: "Name, work email, phone, company, and a brief message.",
6945
+ fieldCount: 5
6946
+ },
6947
+ {
6948
+ id: "event-rsvp",
6949
+ label: "Event RSVP",
6950
+ description: "Name, email, attendance answer, guest count, and dietary notes.",
6951
+ fieldCount: 5
6952
+ },
6953
+ {
6954
+ id: "quote-request",
6955
+ label: "Quote request",
6956
+ description: "Contact info plus service type, budget range, and a details field.",
6957
+ fieldCount: 6
6958
+ }
6959
+ ];
6960
+
6802
6961
  // src/lib/createHubFormSubmit.ts
6803
6962
  function publicBase({ hubUrl, siteId, formSlug }) {
6804
6963
  const cleanHub = hubUrl.replace(/\/+$/, "");
@@ -6894,14 +7053,31 @@ function createHubFormSubmit(opts) {
6894
7053
  }
6895
7054
 
6896
7055
  // src/components/HubForm.tsx
6897
- import { useEffect as useEffect8, useState as useState5 } from "react";
7056
+ import { useEffect as useEffect8, useMemo as useMemo7, useState as useState5 } from "react";
6898
7057
  import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
7058
+ function isRenderableSchema(value) {
7059
+ if (!value || typeof value !== "object") return false;
7060
+ const schema = value;
7061
+ const fields = schema.fields;
7062
+ const steps = schema.steps;
7063
+ if (!fields || typeof fields !== "object" || Object.keys(fields).length === 0) {
7064
+ return false;
7065
+ }
7066
+ if (!steps || typeof steps !== "object" || Object.keys(steps).length === 0) {
7067
+ return false;
7068
+ }
7069
+ return true;
7070
+ }
6899
7071
  function HubForm({
6900
7072
  hubUrl,
6901
7073
  siteId,
6902
7074
  formSlug,
6903
7075
  loadingFallback,
6904
7076
  errorFallback,
7077
+ unconfiguredFallback,
7078
+ successFallback,
7079
+ onSuccess,
7080
+ onError,
6905
7081
  formProps
6906
7082
  }) {
6907
7083
  const [state, setState] = useState5({ status: "loading" });
@@ -6913,7 +7089,12 @@ function HubForm({
6913
7089
  if (!res.ok) throw new Error(`schema fetch failed: ${res.status} ${res.statusText}`);
6914
7090
  return res.json();
6915
7091
  }).then((schema) => {
6916
- if (!cancelled) setState({ status: "ready", schema });
7092
+ if (cancelled) return;
7093
+ if (isRenderableSchema(schema)) {
7094
+ setState({ status: "ready", schema });
7095
+ } else {
7096
+ setState({ status: "unconfigured" });
7097
+ }
6917
7098
  }).catch((err) => {
6918
7099
  if (!cancelled) {
6919
7100
  setState({
@@ -6926,6 +7107,23 @@ function HubForm({
6926
7107
  cancelled = true;
6927
7108
  };
6928
7109
  }, [hubUrl, siteId, formSlug]);
7110
+ const wrappedSubmit = useMemo7(() => {
7111
+ const base = createHubFormSubmit({ hubUrl, siteId, formSlug });
7112
+ return {
7113
+ type: "custom",
7114
+ async onSubmit(values) {
7115
+ try {
7116
+ await base.onSubmit(values);
7117
+ onSuccess?.(values);
7118
+ setState({ status: "submitted" });
7119
+ } catch (err) {
7120
+ const error = err instanceof Error ? err : new Error(String(err));
7121
+ onError?.(error);
7122
+ throw error;
7123
+ }
7124
+ }
7125
+ };
7126
+ }, [hubUrl, siteId, formSlug, onSuccess, onError]);
6929
7127
  if (state.status === "loading") {
6930
7128
  return loadingFallback ?? /* @__PURE__ */ jsx14("div", { "data-saastro-hubform-loading": true, style: loadingStyle, children: "Loading\u2026" });
6931
7129
  }
@@ -6936,9 +7134,17 @@ function HubForm({
6936
7134
  state.error
6937
7135
  ] });
6938
7136
  }
7137
+ if (state.status === "unconfigured") {
7138
+ if (unconfiguredFallback !== void 0) return /* @__PURE__ */ jsx14(Fragment3, { children: unconfiguredFallback });
7139
+ return /* @__PURE__ */ jsx14("div", { role: "status", style: unconfiguredStyle, "data-saastro-hubform-unconfigured": true, children: "This form has not been configured yet. Open it in the Hub builder to add fields." });
7140
+ }
7141
+ if (state.status === "submitted") {
7142
+ if (successFallback !== void 0) return /* @__PURE__ */ jsx14(Fragment3, { children: successFallback });
7143
+ return /* @__PURE__ */ jsx14("div", { role: "status", style: successStyle, "data-saastro-hubform-submitted": true, children: "Thanks \u2014 we'll get back to you soon." });
7144
+ }
6939
7145
  const config = {
6940
7146
  ...state.schema,
6941
- submit: createHubFormSubmit({ hubUrl, siteId, formSlug })
7147
+ submit: wrappedSubmit
6942
7148
  };
6943
7149
  return /* @__PURE__ */ jsx14(Form, { ...formProps, config });
6944
7150
  }
@@ -6958,6 +7164,25 @@ var errorStyle = {
6958
7164
  fontFamily: "system-ui, -apple-system, sans-serif",
6959
7165
  fontSize: "0.875rem"
6960
7166
  };
7167
+ var unconfiguredStyle = {
7168
+ padding: "1rem",
7169
+ background: "#fffbeb",
7170
+ color: "#92400e",
7171
+ border: "1px solid #fcd34d",
7172
+ borderRadius: "0.375rem",
7173
+ fontFamily: "system-ui, -apple-system, sans-serif",
7174
+ fontSize: "0.875rem"
7175
+ };
7176
+ var successStyle = {
7177
+ padding: "1.25rem 1rem",
7178
+ background: "#ecfdf5",
7179
+ color: "#065f46",
7180
+ border: "1px solid #6ee7b7",
7181
+ borderRadius: "0.375rem",
7182
+ fontFamily: "system-ui, -apple-system, sans-serif",
7183
+ fontSize: "0.95rem",
7184
+ textAlign: "center"
7185
+ };
6961
7186
 
6962
7187
  // src/utils/testDataGenerator.ts
6963
7188
  var SPANISH_PATTERNS = [
@@ -7198,6 +7423,8 @@ export {
7198
7423
  StepsAccordion,
7199
7424
  StepsNavigation,
7200
7425
  StepsProgress,
7426
+ TEMPLATES,
7427
+ TEMPLATE_META,
7201
7428
  TYPE_SPECIFIC_PROPERTIES,
7202
7429
  VALIDATION_METHODS,
7203
7430
  analyticsPlugin,
@@ -7211,8 +7438,13 @@ export {
7211
7438
  configureComponents,
7212
7439
  coreComponents,
7213
7440
  createComponentRegistry,
7441
+ createContactTemplate,
7442
+ createEventRsvpTemplate,
7214
7443
  createHubFormSubmit,
7444
+ createLeadGenTemplate,
7215
7445
  createMissingComponentPlaceholder,
7446
+ createNewsletterTemplate,
7447
+ createQuoteRequestTemplate,
7216
7448
  createShadcnRegistry,
7217
7449
  databowlAction,
7218
7450
  databowlPlugin,