@gropulse/booking-widget 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.
@@ -0,0 +1,2 @@
1
+ import type { BookingWidgetProps } from "./types.js";
2
+ export declare function BookingWidget(props: BookingWidgetProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import type { ChatMessage } from "../types.js";
2
+ export declare function ChatBubble({ message }: {
3
+ message: ChatMessage;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ interface InputBarProps {
2
+ onSend: (text: string) => void;
3
+ disabled?: boolean;
4
+ placeholder?: string;
5
+ }
6
+ export declare function InputBar({ onSend, disabled, placeholder }: InputBarProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { ChatMessage } from "../types.js";
2
+ interface MessageListProps {
3
+ messages: ChatMessage[];
4
+ isLoading: boolean;
5
+ }
6
+ export declare function MessageList({ messages, isLoading }: MessageListProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { Slot } from "../types.js";
2
+ interface SlotPickerProps {
3
+ slots: Slot[];
4
+ onPick: (slot: Slot) => void;
5
+ disabled?: boolean;
6
+ }
7
+ export declare function SlotPicker({ slots, onPick, disabled }: SlotPickerProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { ChatMessage, ShopContext, Slot, BookingInfo } from "../types.js";
2
+ interface UseBookingChatOptions {
3
+ apiBaseUrl: string;
4
+ context: ShopContext;
5
+ onBookingConfirmed?: (booking: BookingInfo) => void;
6
+ }
7
+ interface UseBookingChatResult {
8
+ messages: ChatMessage[];
9
+ slots: Slot[] | null;
10
+ booking: BookingInfo | null;
11
+ isLoading: boolean;
12
+ error: string | null;
13
+ sendMessage: (text: string) => Promise<void>;
14
+ pickSlot: (slot: Slot) => Promise<void>;
15
+ start: () => Promise<void>;
16
+ }
17
+ export declare function useBookingChat(opts: UseBookingChatOptions): UseBookingChatResult;
18
+ export {};
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("react");function v(o){const{apiBaseUrl:s,context:n,onBookingConfirmed:t}=o,[g,w]=a.useState([]),[h,l]=a.useState(null),[f,r]=a.useState(null),[c,m]=a.useState(!1),[j,x]=a.useState(null),p=a.useCallback(async b=>{const i={...n,timezone:n.timezone??Intl.DateTimeFormat().resolvedOptions().timeZone},d=await fetch(`${s}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({messages:b,context:i})});if(!d.ok){const u=await d.text();throw new Error(`Chat API error ${d.status}: ${u}`)}return await d.json()},[s,n]),y=a.useCallback(b=>{w(i=>[...i,{role:"assistant",content:b.message}]),b.action==="show_slots"&&b.slots&&l(b.slots),b.action==="booking_confirmed"&&b.booking&&(r(b.booking),l(null),t==null||t(b.booking))},[t]),k=a.useCallback(async()=>{if(!(g.length>0||c)){m(!0),x(null);try{const i=await p([{role:"user",content:"__START__"}]);w([{role:"assistant",content:i.message}]),i.action==="show_slots"&&i.slots&&l(i.slots),i.action==="booking_confirmed"&&i.booking&&(r(i.booking),t==null||t(i.booking))}catch(b){x(b.message)}finally{m(!1)}}},[g.length,c,p,t]),N=a.useCallback(async b=>{const i=b.trim();if(!i||c)return;const d=[...g,{role:"user",content:i}];w(d),m(!0),x(null);try{const u=await p(d);y(u)}catch(u){x(u.message)}finally{m(!1)}},[g,c,p,y]),S=a.useCallback(async b=>{if(c)return;const i=new Date(b.time).toLocaleString(void 0,{weekday:"short",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"}),d=[...g,{role:"user",content:`I'll take ${i} (${b.time})`}];w(d),l(null),m(!0),x(null);try{const u=await p(d);y(u)}catch(u){x(u.message)}finally{m(!1)}},[g,c,p,y]);return{messages:g,slots:h,booking:f,isLoading:c,error:j,sendMessage:N,pickSlot:S,start:k}}function C({message:o}){const s=o.role==="user";return e.jsx("div",{className:`gbw-flex ${s?"gbw-justify-end":"gbw-justify-start"} gbw-mb-2`,children:e.jsx("div",{className:s?"gbw-max-w-[80%] gbw-rounded-2xl gbw-rounded-br-sm gbw-bg-gropulse-navy gbw-px-3.5 gbw-py-2 gbw-text-sm gbw-text-white":"gbw-max-w-[80%] gbw-rounded-2xl gbw-rounded-bl-sm gbw-bg-gropulse-bg gbw-px-3.5 gbw-py-2 gbw-text-sm gbw-text-gropulse-text gbw-border gbw-border-gropulse-border",children:o.content})})}function L({messages:o,isLoading:s}){const n=a.useRef(null);return a.useEffect(()=>{var t;(t=n.current)==null||t.scrollIntoView({behavior:"smooth",block:"end"})},[o,s]),e.jsxs("div",{className:"gbw-flex-1 gbw-overflow-y-auto gbw-px-4 gbw-py-3",children:[o.map((t,g)=>e.jsx(C,{message:t},g)),s?e.jsx(B,{}):null,e.jsx("div",{ref:n})]})}function B(){return e.jsx("div",{className:"gbw-flex gbw-justify-start gbw-mb-2",children:e.jsx("div",{className:"gbw-rounded-2xl gbw-rounded-bl-sm gbw-bg-gropulse-bg gbw-border gbw-border-gropulse-border gbw-px-3.5 gbw-py-2.5",children:e.jsxs("div",{className:"gbw-flex gbw-gap-1",children:[e.jsx("span",{className:"gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block"}),e.jsx("span",{className:"gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block"}),e.jsx("span",{className:"gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block"})]})})})}function T({onSend:o,disabled:s,placeholder:n}){const[t,g]=a.useState(""),w=()=>{const l=t.trim();!l||s||(o(l),g(""))},h=l=>{l.key==="Enter"&&!l.shiftKey&&(l.preventDefault(),w())};return e.jsx("div",{className:"gbw-border-t gbw-border-gropulse-border gbw-bg-white gbw-p-3",children:e.jsxs("div",{className:"gbw-flex gbw-items-end gbw-gap-2",children:[e.jsx("textarea",{value:t,onChange:l=>g(l.target.value),onKeyDown:h,disabled:s,rows:1,placeholder:n??"Type a message…",className:"gbw-flex-1 gbw-resize-none gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-white gbw-px-3 gbw-py-2 gbw-text-sm gbw-outline-none focus:gbw-border-gropulse-navy focus:gbw-ring-2 focus:gbw-ring-gropulse-navy/10 disabled:gbw-bg-gropulse-bg disabled:gbw-text-gropulse-muted",style:{maxHeight:120}}),e.jsx("button",{type:"button",onClick:w,disabled:s||!t.trim(),className:"gbw-shrink-0 gbw-rounded-xl gbw-bg-gropulse-navy gbw-px-4 gbw-py-2 gbw-text-sm gbw-font-medium gbw-text-white gbw-transition hover:gbw-bg-gropulse-navyHover disabled:gbw-opacity-40",children:"Send"})]})})}function D({slots:o,onPick:s,disabled:n}){const t=a.useMemo(()=>M(o),[o]);return o.length===0?e.jsx("div",{className:"gbw-mx-4 gbw-mb-3 gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-gropulse-bg gbw-px-3 gbw-py-2 gbw-text-sm gbw-text-gropulse-muted",children:"No slots available right now — try again in a moment."}):e.jsxs("div",{className:"gbw-mx-4 gbw-mb-3 gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-white gbw-p-3",children:[e.jsx("div",{className:"gbw-mb-2 gbw-text-xs gbw-font-medium gbw-uppercase gbw-tracking-wide gbw-text-gropulse-muted",children:"Pick a time that works"}),e.jsx("div",{className:"gbw-flex gbw-flex-col gbw-gap-3 gbw-max-h-60 gbw-overflow-y-auto",children:t.map(g=>e.jsxs("div",{children:[e.jsx("div",{className:"gbw-mb-1.5 gbw-text-xs gbw-font-semibold gbw-text-gropulse-text",children:g.dayLabel}),e.jsx("div",{className:"gbw-grid gbw-grid-cols-3 gbw-gap-1.5",children:g.slots.map(w=>e.jsx("button",{type:"button",disabled:n,onClick:()=>s(w),className:"gbw-rounded-lg gbw-border gbw-border-gropulse-border gbw-bg-white gbw-px-2 gbw-py-1.5 gbw-text-xs gbw-font-medium gbw-text-gropulse-text gbw-transition hover:gbw-border-gropulse-navy hover:gbw-bg-gropulse-navy hover:gbw-text-white disabled:gbw-opacity-40",children:_(w.time)},w.time))})]},g.dayLabel))})]})}function M(o){const s=new Map;for(const n of o){const g=new Date(n.time).toLocaleDateString(void 0,{weekday:"short",month:"short",day:"numeric"}),w=s.get(g);w?w.push(n):s.set(g,[n])}return Array.from(s.entries()).map(([n,t])=>({dayLabel:n,slots:t}))}function _(o){return new Date(o).toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit"})}function I(o){const{apiBaseUrl:s,context:n,trigger:t="button",buttonLabel:g="Book Free Strategy Call",onBookingConfirmed:w,className:h}=o,[l,f]=a.useState(t==="auto"),r=v({apiBaseUrl:s,context:n,onBookingConfirmed:w});return a.useEffect(()=>{l&&r.messages.length===0&&!r.isLoading&&r.start()},[l,r]),e.jsxs("div",{className:`gbw-root ${h??""}`,children:[t==="button"?e.jsxs("button",{type:"button",onClick:()=>f(c=>!c),className:"gbw-inline-flex gbw-items-center gbw-gap-2 gbw-rounded-xl gbw-bg-gropulse-navy gbw-px-4 gbw-py-2.5 gbw-text-sm gbw-font-medium gbw-text-white gbw-shadow-sm gbw-transition hover:gbw-bg-gropulse-navyHover",children:[e.jsx("span",{className:"gbw-h-2 gbw-w-2 gbw-rounded-full gbw-bg-gropulse-accent"}),g]}):null,l?e.jsxs("div",{className:"gbw-fixed gbw-inset-0 gbw-z-[9999] gbw-flex gbw-items-end gbw-justify-end gbw-p-0 sm:gbw-p-4",children:[e.jsx("div",{className:"gbw-absolute gbw-inset-0 gbw-bg-black/20",onClick:()=>f(!1)}),e.jsxs("div",{className:"gbw-relative gbw-flex gbw-h-full gbw-w-full gbw-flex-col gbw-overflow-hidden gbw-bg-white gbw-shadow-panel sm:gbw-h-[620px] sm:gbw-w-[400px] sm:gbw-rounded-2xl",children:[e.jsx($,{ownerName:n.ownerName,onClose:()=>f(!1)}),e.jsx(L,{messages:r.messages,isLoading:r.isLoading}),r.slots&&!r.booking?e.jsx(D,{slots:r.slots,onPick:r.pickSlot,disabled:r.isLoading}):null,r.error?e.jsx("div",{className:"gbw-mx-4 gbw-mb-2 gbw-rounded-lg gbw-border gbw-border-red-200 gbw-bg-red-50 gbw-px-3 gbw-py-2 gbw-text-xs gbw-text-red-700",children:r.error}):null,r.booking?e.jsx(P,{}):e.jsx(T,{onSend:r.sendMessage,disabled:r.isLoading,placeholder:"Reply…"})]})]}):null]})}function $({ownerName:o,onClose:s}){return e.jsxs("div",{className:"gbw-flex gbw-items-center gbw-justify-between gbw-border-b gbw-border-gropulse-border gbw-bg-gropulse-navy gbw-px-4 gbw-py-3 gbw-text-white",children:[e.jsxs("div",{children:[e.jsx("div",{className:"gbw-text-sm gbw-font-semibold",children:"Gropulse Growth Strategy"}),e.jsxs("div",{className:"gbw-text-xs gbw-text-white/70",children:["Hey ",o.split(" ")[0]," — let’s find a time."]})]}),e.jsx("button",{type:"button",onClick:s,"aria-label":"Close",className:"gbw-rounded-md gbw-p-1 gbw-text-white/80 gbw-transition hover:gbw-bg-white/10 hover:gbw-text-white",children:e.jsxs("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M18 6 6 18"}),e.jsx("path",{d:"m6 6 12 12"})]})})]})}function P(){return e.jsx("div",{className:"gbw-border-t gbw-border-gropulse-border gbw-bg-gropulse-bg gbw-px-4 gbw-py-3 gbw-text-center gbw-text-xs gbw-text-gropulse-muted",children:"Calendar invite on its way. You can close this window."})}exports.BookingWidget=I;exports.useBookingChat=v;
@@ -0,0 +1,4 @@
1
+ import "./styles/widget.css";
2
+ export { BookingWidget } from "./BookingWidget.js";
3
+ export { useBookingChat } from "./hooks/useBookingChat.js";
4
+ export type { BookingWidgetProps, BookingWidgetTrigger, ShopContext, ChatMessage, Slot, BookingInfo, ChatApiResponse, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,289 @@
1
+ import { jsx as e, jsxs as a } from "react/jsx-runtime";
2
+ import { useState as m, useCallback as y, useRef as T, useEffect as N, useMemo as B } from "react";
3
+ function D(g) {
4
+ const { apiBaseUrl: o, context: n, onBookingConfirmed: t } = g, [s, w] = m([]), [v, i] = m(null), [f, r] = m(null), [c, p] = m(!1), [C, h] = m(null), x = y(
5
+ async (b) => {
6
+ const l = {
7
+ ...n,
8
+ timezone: n.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone
9
+ }, d = await fetch(`${o}/api/chat`, {
10
+ method: "POST",
11
+ headers: { "Content-Type": "application/json" },
12
+ body: JSON.stringify({ messages: b, context: l })
13
+ });
14
+ if (!d.ok) {
15
+ const u = await d.text();
16
+ throw new Error(`Chat API error ${d.status}: ${u}`);
17
+ }
18
+ return await d.json();
19
+ },
20
+ [o, n]
21
+ ), k = y(
22
+ (b) => {
23
+ w((l) => [...l, { role: "assistant", content: b.message }]), b.action === "show_slots" && b.slots && i(b.slots), b.action === "booking_confirmed" && b.booking && (r(b.booking), i(null), t == null || t(b.booking));
24
+ },
25
+ [t]
26
+ ), L = y(async () => {
27
+ if (!(s.length > 0 || c)) {
28
+ p(!0), h(null);
29
+ try {
30
+ const l = await x([{
31
+ role: "user",
32
+ content: "__START__"
33
+ }]);
34
+ w([{ role: "assistant", content: l.message }]), l.action === "show_slots" && l.slots && i(l.slots), l.action === "booking_confirmed" && l.booking && (r(l.booking), t == null || t(l.booking));
35
+ } catch (b) {
36
+ h(b.message);
37
+ } finally {
38
+ p(!1);
39
+ }
40
+ }
41
+ }, [s.length, c, x, t]), S = y(
42
+ async (b) => {
43
+ const l = b.trim();
44
+ if (!l || c) return;
45
+ const d = [...s, { role: "user", content: l }];
46
+ w(d), p(!0), h(null);
47
+ try {
48
+ const u = await x(d);
49
+ k(u);
50
+ } catch (u) {
51
+ h(u.message);
52
+ } finally {
53
+ p(!1);
54
+ }
55
+ },
56
+ [s, c, x, k]
57
+ ), j = y(
58
+ async (b) => {
59
+ if (c) return;
60
+ const l = new Date(b.time).toLocaleString(void 0, {
61
+ weekday: "short",
62
+ month: "short",
63
+ day: "numeric",
64
+ hour: "numeric",
65
+ minute: "2-digit"
66
+ }), d = [
67
+ ...s,
68
+ { role: "user", content: `I'll take ${l} (${b.time})` }
69
+ ];
70
+ w(d), i(null), p(!0), h(null);
71
+ try {
72
+ const u = await x(d);
73
+ k(u);
74
+ } catch (u) {
75
+ h(u.message);
76
+ } finally {
77
+ p(!1);
78
+ }
79
+ },
80
+ [s, c, x, k]
81
+ );
82
+ return { messages: s, slots: v, booking: f, isLoading: c, error: C, sendMessage: S, pickSlot: j, start: L };
83
+ }
84
+ function _({ message: g }) {
85
+ const o = g.role === "user";
86
+ return /* @__PURE__ */ e("div", { className: `gbw-flex ${o ? "gbw-justify-end" : "gbw-justify-start"} gbw-mb-2`, children: /* @__PURE__ */ e(
87
+ "div",
88
+ {
89
+ className: o ? "gbw-max-w-[80%] gbw-rounded-2xl gbw-rounded-br-sm gbw-bg-gropulse-navy gbw-px-3.5 gbw-py-2 gbw-text-sm gbw-text-white" : "gbw-max-w-[80%] gbw-rounded-2xl gbw-rounded-bl-sm gbw-bg-gropulse-bg gbw-px-3.5 gbw-py-2 gbw-text-sm gbw-text-gropulse-text gbw-border gbw-border-gropulse-border",
90
+ children: g.content
91
+ }
92
+ ) });
93
+ }
94
+ function I({ messages: g, isLoading: o }) {
95
+ const n = T(null);
96
+ return N(() => {
97
+ var t;
98
+ (t = n.current) == null || t.scrollIntoView({ behavior: "smooth", block: "end" });
99
+ }, [g, o]), /* @__PURE__ */ a("div", { className: "gbw-flex-1 gbw-overflow-y-auto gbw-px-4 gbw-py-3", children: [
100
+ g.map((t, s) => /* @__PURE__ */ e(_, { message: t }, s)),
101
+ o ? /* @__PURE__ */ e(M, {}) : null,
102
+ /* @__PURE__ */ e("div", { ref: n })
103
+ ] });
104
+ }
105
+ function M() {
106
+ return /* @__PURE__ */ e("div", { className: "gbw-flex gbw-justify-start gbw-mb-2", children: /* @__PURE__ */ e("div", { className: "gbw-rounded-2xl gbw-rounded-bl-sm gbw-bg-gropulse-bg gbw-border gbw-border-gropulse-border gbw-px-3.5 gbw-py-2.5", children: /* @__PURE__ */ a("div", { className: "gbw-flex gbw-gap-1", children: [
107
+ /* @__PURE__ */ e("span", { className: "gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block" }),
108
+ /* @__PURE__ */ e("span", { className: "gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block" }),
109
+ /* @__PURE__ */ e("span", { className: "gbw-typing-dot gbw-h-1.5 gbw-w-1.5 gbw-rounded-full gbw-bg-gropulse-muted gbw-inline-block" })
110
+ ] }) }) });
111
+ }
112
+ function $({ onSend: g, disabled: o, placeholder: n }) {
113
+ const [t, s] = m(""), w = () => {
114
+ const i = t.trim();
115
+ !i || o || (g(i), s(""));
116
+ };
117
+ return /* @__PURE__ */ e("div", { className: "gbw-border-t gbw-border-gropulse-border gbw-bg-white gbw-p-3", children: /* @__PURE__ */ a("div", { className: "gbw-flex gbw-items-end gbw-gap-2", children: [
118
+ /* @__PURE__ */ e(
119
+ "textarea",
120
+ {
121
+ value: t,
122
+ onChange: (i) => s(i.target.value),
123
+ onKeyDown: (i) => {
124
+ i.key === "Enter" && !i.shiftKey && (i.preventDefault(), w());
125
+ },
126
+ disabled: o,
127
+ rows: 1,
128
+ placeholder: n ?? "Type a message…",
129
+ className: "gbw-flex-1 gbw-resize-none gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-white gbw-px-3 gbw-py-2 gbw-text-sm gbw-outline-none focus:gbw-border-gropulse-navy focus:gbw-ring-2 focus:gbw-ring-gropulse-navy/10 disabled:gbw-bg-gropulse-bg disabled:gbw-text-gropulse-muted",
130
+ style: { maxHeight: 120 }
131
+ }
132
+ ),
133
+ /* @__PURE__ */ e(
134
+ "button",
135
+ {
136
+ type: "button",
137
+ onClick: w,
138
+ disabled: o || !t.trim(),
139
+ className: "gbw-shrink-0 gbw-rounded-xl gbw-bg-gropulse-navy gbw-px-4 gbw-py-2 gbw-text-sm gbw-font-medium gbw-text-white gbw-transition hover:gbw-bg-gropulse-navyHover disabled:gbw-opacity-40",
140
+ children: "Send"
141
+ }
142
+ )
143
+ ] }) });
144
+ }
145
+ function z({ slots: g, onPick: o, disabled: n }) {
146
+ const t = B(() => H(g), [g]);
147
+ return g.length === 0 ? /* @__PURE__ */ e("div", { className: "gbw-mx-4 gbw-mb-3 gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-gropulse-bg gbw-px-3 gbw-py-2 gbw-text-sm gbw-text-gropulse-muted", children: "No slots available right now — try again in a moment." }) : /* @__PURE__ */ a("div", { className: "gbw-mx-4 gbw-mb-3 gbw-rounded-xl gbw-border gbw-border-gropulse-border gbw-bg-white gbw-p-3", children: [
148
+ /* @__PURE__ */ e("div", { className: "gbw-mb-2 gbw-text-xs gbw-font-medium gbw-uppercase gbw-tracking-wide gbw-text-gropulse-muted", children: "Pick a time that works" }),
149
+ /* @__PURE__ */ e("div", { className: "gbw-flex gbw-flex-col gbw-gap-3 gbw-max-h-60 gbw-overflow-y-auto", children: t.map((s) => /* @__PURE__ */ a("div", { children: [
150
+ /* @__PURE__ */ e("div", { className: "gbw-mb-1.5 gbw-text-xs gbw-font-semibold gbw-text-gropulse-text", children: s.dayLabel }),
151
+ /* @__PURE__ */ e("div", { className: "gbw-grid gbw-grid-cols-3 gbw-gap-1.5", children: s.slots.map((w) => /* @__PURE__ */ e(
152
+ "button",
153
+ {
154
+ type: "button",
155
+ disabled: n,
156
+ onClick: () => o(w),
157
+ className: "gbw-rounded-lg gbw-border gbw-border-gropulse-border gbw-bg-white gbw-px-2 gbw-py-1.5 gbw-text-xs gbw-font-medium gbw-text-gropulse-text gbw-transition hover:gbw-border-gropulse-navy hover:gbw-bg-gropulse-navy hover:gbw-text-white disabled:gbw-opacity-40",
158
+ children: P(w.time)
159
+ },
160
+ w.time
161
+ )) })
162
+ ] }, s.dayLabel)) })
163
+ ] });
164
+ }
165
+ function H(g) {
166
+ const o = /* @__PURE__ */ new Map();
167
+ for (const n of g) {
168
+ const s = new Date(n.time).toLocaleDateString(void 0, {
169
+ weekday: "short",
170
+ month: "short",
171
+ day: "numeric"
172
+ }), w = o.get(s);
173
+ w ? w.push(n) : o.set(s, [n]);
174
+ }
175
+ return Array.from(o.entries()).map(([n, t]) => ({ dayLabel: n, slots: t }));
176
+ }
177
+ function P(g) {
178
+ return new Date(g).toLocaleTimeString(void 0, {
179
+ hour: "numeric",
180
+ minute: "2-digit"
181
+ });
182
+ }
183
+ function A(g) {
184
+ const {
185
+ apiBaseUrl: o,
186
+ context: n,
187
+ trigger: t = "button",
188
+ buttonLabel: s = "Book Free Strategy Call",
189
+ onBookingConfirmed: w,
190
+ className: v
191
+ } = g, [i, f] = m(t === "auto"), r = D({ apiBaseUrl: o, context: n, onBookingConfirmed: w });
192
+ return N(() => {
193
+ i && r.messages.length === 0 && !r.isLoading && r.start();
194
+ }, [i, r]), /* @__PURE__ */ a("div", { className: `gbw-root ${v ?? ""}`, children: [
195
+ t === "button" ? /* @__PURE__ */ a(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ onClick: () => f((c) => !c),
200
+ className: "gbw-inline-flex gbw-items-center gbw-gap-2 gbw-rounded-xl gbw-bg-gropulse-navy gbw-px-4 gbw-py-2.5 gbw-text-sm gbw-font-medium gbw-text-white gbw-shadow-sm gbw-transition hover:gbw-bg-gropulse-navyHover",
201
+ children: [
202
+ /* @__PURE__ */ e("span", { className: "gbw-h-2 gbw-w-2 gbw-rounded-full gbw-bg-gropulse-accent" }),
203
+ s
204
+ ]
205
+ }
206
+ ) : null,
207
+ i ? /* @__PURE__ */ a("div", { className: "gbw-fixed gbw-inset-0 gbw-z-[9999] gbw-flex gbw-items-end gbw-justify-end gbw-p-0 sm:gbw-p-4", children: [
208
+ /* @__PURE__ */ e(
209
+ "div",
210
+ {
211
+ className: "gbw-absolute gbw-inset-0 gbw-bg-black/20",
212
+ onClick: () => f(!1)
213
+ }
214
+ ),
215
+ /* @__PURE__ */ a("div", { className: "gbw-relative gbw-flex gbw-h-full gbw-w-full gbw-flex-col gbw-overflow-hidden gbw-bg-white gbw-shadow-panel sm:gbw-h-[620px] sm:gbw-w-[400px] sm:gbw-rounded-2xl", children: [
216
+ /* @__PURE__ */ e(
217
+ R,
218
+ {
219
+ ownerName: n.ownerName,
220
+ onClose: () => f(!1)
221
+ }
222
+ ),
223
+ /* @__PURE__ */ e(I, { messages: r.messages, isLoading: r.isLoading }),
224
+ r.slots && !r.booking ? /* @__PURE__ */ e(
225
+ z,
226
+ {
227
+ slots: r.slots,
228
+ onPick: r.pickSlot,
229
+ disabled: r.isLoading
230
+ }
231
+ ) : null,
232
+ r.error ? /* @__PURE__ */ e("div", { className: "gbw-mx-4 gbw-mb-2 gbw-rounded-lg gbw-border gbw-border-red-200 gbw-bg-red-50 gbw-px-3 gbw-py-2 gbw-text-xs gbw-text-red-700", children: r.error }) : null,
233
+ r.booking ? /* @__PURE__ */ e(E, {}) : /* @__PURE__ */ e(
234
+ $,
235
+ {
236
+ onSend: r.sendMessage,
237
+ disabled: r.isLoading,
238
+ placeholder: "Reply…"
239
+ }
240
+ )
241
+ ] })
242
+ ] }) : null
243
+ ] });
244
+ }
245
+ function R({ ownerName: g, onClose: o }) {
246
+ return /* @__PURE__ */ a("div", { className: "gbw-flex gbw-items-center gbw-justify-between gbw-border-b gbw-border-gropulse-border gbw-bg-gropulse-navy gbw-px-4 gbw-py-3 gbw-text-white", children: [
247
+ /* @__PURE__ */ a("div", { children: [
248
+ /* @__PURE__ */ e("div", { className: "gbw-text-sm gbw-font-semibold", children: "Gropulse Growth Strategy" }),
249
+ /* @__PURE__ */ a("div", { className: "gbw-text-xs gbw-text-white/70", children: [
250
+ "Hey ",
251
+ g.split(" ")[0],
252
+ " — let’s find a time."
253
+ ] })
254
+ ] }),
255
+ /* @__PURE__ */ e(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ onClick: o,
260
+ "aria-label": "Close",
261
+ className: "gbw-rounded-md gbw-p-1 gbw-text-white/80 gbw-transition hover:gbw-bg-white/10 hover:gbw-text-white",
262
+ children: /* @__PURE__ */ a(
263
+ "svg",
264
+ {
265
+ width: "18",
266
+ height: "18",
267
+ viewBox: "0 0 24 24",
268
+ fill: "none",
269
+ stroke: "currentColor",
270
+ strokeWidth: "2",
271
+ strokeLinecap: "round",
272
+ strokeLinejoin: "round",
273
+ children: [
274
+ /* @__PURE__ */ e("path", { d: "M18 6 6 18" }),
275
+ /* @__PURE__ */ e("path", { d: "m6 6 12 12" })
276
+ ]
277
+ }
278
+ )
279
+ }
280
+ )
281
+ ] });
282
+ }
283
+ function E() {
284
+ return /* @__PURE__ */ e("div", { className: "gbw-border-t gbw-border-gropulse-border gbw-bg-gropulse-bg gbw-px-4 gbw-py-3 gbw-text-center gbw-text-xs gbw-text-gropulse-muted", children: "Calendar invite on its way. You can close this window." });
285
+ }
286
+ export {
287
+ A as BookingWidget,
288
+ D as useBookingChat
289
+ };
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.gbw-root{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;color:#0f172a}.gbw-typing-dot{animation:gbw-typing 1.4s infinite ease-in-out both}.gbw-typing-dot:nth-child(2){animation-delay:.15s}.gbw-typing-dot:nth-child(3){animation-delay:.3s}.gbw-fixed{position:fixed}.gbw-absolute{position:absolute}.gbw-relative{position:relative}.gbw-inset-0{top:0;right:0;bottom:0;left:0}.gbw-z-\[9999\]{z-index:9999}.gbw-mx-4{margin-left:1rem;margin-right:1rem}.gbw-mb-1\.5{margin-bottom:.375rem}.gbw-mb-2{margin-bottom:.5rem}.gbw-mb-3{margin-bottom:.75rem}.gbw-inline-block{display:inline-block}.gbw-flex{display:flex}.gbw-inline-flex{display:inline-flex}.gbw-grid{display:grid}.gbw-h-1\.5{height:.375rem}.gbw-h-2{height:.5rem}.gbw-h-full{height:100%}.gbw-max-h-60{max-height:15rem}.gbw-w-1\.5{width:.375rem}.gbw-w-2{width:.5rem}.gbw-w-full{width:100%}.gbw-max-w-\[80\%\]{max-width:80%}.gbw-flex-1{flex:1 1 0%}.gbw-shrink-0{flex-shrink:0}.gbw-resize-none{resize:none}.gbw-grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.gbw-flex-col{flex-direction:column}.gbw-items-end{align-items:flex-end}.gbw-items-center{align-items:center}.gbw-justify-start{justify-content:flex-start}.gbw-justify-end{justify-content:flex-end}.gbw-justify-between{justify-content:space-between}.gbw-gap-1{gap:.25rem}.gbw-gap-1\.5{gap:.375rem}.gbw-gap-2{gap:.5rem}.gbw-gap-3{gap:.75rem}.gbw-overflow-hidden{overflow:hidden}.gbw-overflow-y-auto{overflow-y:auto}.gbw-rounded-2xl{border-radius:1rem}.gbw-rounded-full{border-radius:9999px}.gbw-rounded-lg{border-radius:.5rem}.gbw-rounded-md{border-radius:.375rem}.gbw-rounded-xl{border-radius:.75rem}.gbw-rounded-bl-sm{border-bottom-left-radius:.125rem}.gbw-rounded-br-sm{border-bottom-right-radius:.125rem}.gbw-border{border-width:1px}.gbw-border-b{border-bottom-width:1px}.gbw-border-t{border-top-width:1px}.gbw-border-gropulse-border{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.gbw-border-red-200{--tw-border-opacity: 1;border-color:rgb(254 202 202 / var(--tw-border-opacity, 1))}.gbw-bg-black\/20{background-color:#0003}.gbw-bg-gropulse-accent{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.gbw-bg-gropulse-bg{--tw-bg-opacity: 1;background-color:rgb(247 248 251 / var(--tw-bg-opacity, 1))}.gbw-bg-gropulse-muted{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity, 1))}.gbw-bg-gropulse-navy{--tw-bg-opacity: 1;background-color:rgb(15 29 58 / var(--tw-bg-opacity, 1))}.gbw-bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.gbw-bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.gbw-p-0{padding:0}.gbw-p-1{padding:.25rem}.gbw-p-3{padding:.75rem}.gbw-px-2{padding-left:.5rem;padding-right:.5rem}.gbw-px-3{padding-left:.75rem;padding-right:.75rem}.gbw-px-3\.5{padding-left:.875rem;padding-right:.875rem}.gbw-px-4{padding-left:1rem;padding-right:1rem}.gbw-py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.gbw-py-2{padding-top:.5rem;padding-bottom:.5rem}.gbw-py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.gbw-py-3{padding-top:.75rem;padding-bottom:.75rem}.gbw-text-center{text-align:center}.gbw-text-sm{font-size:.875rem;line-height:1.25rem}.gbw-text-xs{font-size:.75rem;line-height:1rem}.gbw-font-medium{font-weight:500}.gbw-font-semibold{font-weight:600}.gbw-uppercase{text-transform:uppercase}.gbw-tracking-wide{letter-spacing:.025em}.gbw-text-gropulse-muted{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.gbw-text-gropulse-text{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity, 1))}.gbw-text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.gbw-text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.gbw-text-white\/70{color:#ffffffb3}.gbw-text-white\/80{color:#fffc}.gbw-shadow-panel{--tw-shadow: 0 20px 60px -10px rgba(15, 29, 58, .25);--tw-shadow-colored: 0 20px 60px -10px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gbw-shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.gbw-outline-none{outline:2px solid transparent;outline-offset:2px}.gbw-transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes gbw-typing{0%,80%,to{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}.hover\:gbw-border-gropulse-navy:hover{--tw-border-opacity: 1;border-color:rgb(15 29 58 / var(--tw-border-opacity, 1))}.hover\:gbw-bg-gropulse-navy:hover{--tw-bg-opacity: 1;background-color:rgb(15 29 58 / var(--tw-bg-opacity, 1))}.hover\:gbw-bg-gropulse-navyHover:hover{--tw-bg-opacity: 1;background-color:rgb(20 42 82 / var(--tw-bg-opacity, 1))}.hover\:gbw-bg-white\/10:hover{background-color:#ffffff1a}.hover\:gbw-text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.focus\:gbw-border-gropulse-navy:focus{--tw-border-opacity: 1;border-color:rgb(15 29 58 / var(--tw-border-opacity, 1))}.focus\:gbw-ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:gbw-ring-gropulse-navy\/10:focus{--tw-ring-color: rgb(15 29 58 / .1)}.disabled\:gbw-bg-gropulse-bg:disabled{--tw-bg-opacity: 1;background-color:rgb(247 248 251 / var(--tw-bg-opacity, 1))}.disabled\:gbw-text-gropulse-muted:disabled{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity, 1))}.disabled\:gbw-opacity-40:disabled{opacity:.4}@media (min-width: 640px){.sm\:gbw-h-\[620px\]{height:620px}.sm\:gbw-w-\[400px\]{width:400px}.sm\:gbw-rounded-2xl{border-radius:1rem}.sm\:gbw-p-4{padding:1rem}}
@@ -0,0 +1,42 @@
1
+ export interface ShopContext {
2
+ appName: string;
3
+ shopDomain: string;
4
+ ownerName: string;
5
+ email: string;
6
+ plan: string;
7
+ installedSince: string;
8
+ extraContext?: Record<string, unknown>;
9
+ timezone?: string;
10
+ }
11
+ export interface ChatMessage {
12
+ role: "user" | "assistant";
13
+ content: string;
14
+ }
15
+ export interface Slot {
16
+ time: string;
17
+ }
18
+ export interface BookingInfo {
19
+ id: string | number;
20
+ start: string;
21
+ attendeeEmail: string;
22
+ }
23
+ export interface ChatApiResponse {
24
+ message: string;
25
+ action: null | "show_slots" | "booking_confirmed";
26
+ slots?: Slot[];
27
+ booking?: BookingInfo;
28
+ leadSummary?: {
29
+ growthChallenge?: string;
30
+ revenueRange?: string;
31
+ callGoals?: string;
32
+ };
33
+ }
34
+ export type BookingWidgetTrigger = "button" | "auto";
35
+ export interface BookingWidgetProps {
36
+ apiBaseUrl: string;
37
+ context: ShopContext;
38
+ trigger?: BookingWidgetTrigger;
39
+ buttonLabel?: string;
40
+ onBookingConfirmed?: (booking: BookingInfo) => void;
41
+ className?: string;
42
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@gropulse/booking-widget",
3
+ "version": "0.1.0",
4
+ "description": "Gropulse booking chat widget — embeds in Shopify apps to book free growth strategy calls",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./styles.css": "./dist/styles.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "dev": "vite",
23
+ "build": "vite build --mode lib && tsc -p tsconfig.build.json --emitDeclarationOnly",
24
+ "typecheck": "tsc --noEmit",
25
+ "prepublishOnly": "npm run build"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=18",
32
+ "react-dom": ">=18"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^18.3.12",
36
+ "@types/react-dom": "^18.3.1",
37
+ "@vitejs/plugin-react": "^4.3.3",
38
+ "autoprefixer": "^10.4.20",
39
+ "postcss": "^8.4.49",
40
+ "react": "^18.3.1",
41
+ "react-dom": "^18.3.1",
42
+ "tailwindcss": "^3.4.15",
43
+ "typescript": "^5.6.3",
44
+ "vite": "^5.4.11",
45
+ "vite-plugin-dts": "^4.3.0"
46
+ }
47
+ }