@reeka/minisite-popup 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 ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2026 Reeka. All rights reserved.
2
+
3
+ This software is proprietary. Unauthorized copying, modification,
4
+ distribution, or use is prohibited without prior written permission
5
+ from Reeka.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @reeka/minisite-popup
2
+
3
+ Image lightbox marketing popup for Reeka mini-sites and agent listing sites.
4
+
5
+ ```ts
6
+ import "@reeka/minisite-popup/style.css";
7
+ import { installMinisitePopup } from "@reeka/minisite-popup";
8
+
9
+ app.mount("#app");
10
+
11
+ void installMinisitePopup({
12
+ apiBase: config.apiBaseUrl,
13
+ organizationId: config.portfolioId, // or agentListingId / publicKey
14
+ });
15
+ ```
16
+
17
+ ## Behavior
18
+
19
+ - Fetches `GET /public/marketing-popup/config?organizationId=`
20
+ - Shows once after `delayMs` (default 2500) when enabled
21
+ - Dismiss (X / Escape / backdrop) → `sessionStorage` key `reeka_popup_dismissed:{organizationId}`
22
+ - Skips booking/checkout routes by default
23
+ - CMS edit session: force-show preview; listens for `reeka-marketing-popup-draft` CustomEvent
24
+
25
+ Configure under Settings → Website → Marketing popup (or CMS **Popup** panel).
@@ -0,0 +1,8 @@
1
+ import { type App } from "vue";
2
+ import type { MinisitePopupInstallOptions } from "./types";
3
+ /**
4
+ * Mount the marketing popup when the org has it enabled
5
+ * (checked via public config API). Safe to call after `app.mount`.
6
+ */
7
+ export declare function installMinisitePopup(options: MinisitePopupInstallOptions): Promise<App | null>;
8
+ export type { MinisitePopupInstallOptions, MarketingPopupConfig, } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ import { defineComponent as z, ref as k, computed as E, watch as D, onMounted as K, onUnmounted as N, openBlock as f, createBlock as R, Teleport as A, createElementBlock as g, withKeys as P, withModifiers as C, createElementVNode as y, createCommentVNode as b, toDisplayString as V, createApp as j } from "vue";
2
+ const q = ["onKeydown"], F = ["src"], x = ["href", "target", "rel"], O = "reeka_site_cms_session", B = "reeka-marketing-popup-draft", W = /* @__PURE__ */ z({
3
+ __name: "PopupWidget",
4
+ props: {
5
+ apiBase: {},
6
+ organizationId: {},
7
+ skipBookingRoutes: { type: Boolean }
8
+ },
9
+ setup(i) {
10
+ const a = i, t = k(null), s = k(!1), u = k(null);
11
+ let l = null, r = null;
12
+ const _ = E(
13
+ () => `reeka_popup_dismissed:${a.organizationId}`
14
+ ), I = E(
15
+ () => {
16
+ var e;
17
+ return !!((e = t.value) != null && e.ctaEnabled) && !!t.value.ctaLabel && !!t.value.ctaUrl;
18
+ }
19
+ );
20
+ function c() {
21
+ try {
22
+ return !!sessionStorage.getItem(O);
23
+ } catch {
24
+ return !1;
25
+ }
26
+ }
27
+ function S() {
28
+ if (c()) return !1;
29
+ try {
30
+ return sessionStorage.getItem(_.value) === "1";
31
+ } catch {
32
+ return !1;
33
+ }
34
+ }
35
+ function h() {
36
+ if (!c())
37
+ try {
38
+ sessionStorage.setItem(_.value, "1");
39
+ } catch {
40
+ }
41
+ }
42
+ function T() {
43
+ if (a.skipBookingRoutes === !1) return !1;
44
+ const e = (window.location.pathname || "").toLowerCase();
45
+ return /\/booking(\/|$)/.test(e) || /\/checkout(\/|$)/.test(e) || /\/book(\/|$)/.test(e) || /\/payment(\/|$)/.test(e);
46
+ }
47
+ function d(e) {
48
+ if (!(e != null && e.enabled) || !e.imageUrl) {
49
+ t.value = null, s.value = !1;
50
+ return;
51
+ }
52
+ t.value = e, M();
53
+ }
54
+ function M() {
55
+ var o;
56
+ if (l && (clearTimeout(l), l = null), !((o = t.value) != null && o.enabled) || !t.value.imageUrl || !c() && (S() || T()))
57
+ return;
58
+ const e = typeof t.value.delayMs == "number" && Number.isFinite(t.value.delayMs) ? Math.max(0, Math.min(3e4, t.value.delayMs)) : 2500, n = c() ? 0 : e;
59
+ l = setTimeout(() => {
60
+ s.value = !0;
61
+ }, n);
62
+ }
63
+ function m() {
64
+ s.value = !1, h(), U();
65
+ }
66
+ function L() {
67
+ h();
68
+ }
69
+ function v(e) {
70
+ if (!s.value || e.key !== "Tab" || !u.value) return;
71
+ const n = u.value.querySelectorAll(
72
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
73
+ );
74
+ if (!n.length) return;
75
+ const o = n[0], p = n[n.length - 1];
76
+ e.shiftKey && document.activeElement === o ? (e.preventDefault(), p.focus()) : !e.shiftKey && document.activeElement === p && (e.preventDefault(), o.focus());
77
+ }
78
+ function U() {
79
+ var e;
80
+ (e = r == null ? void 0 : r.focus) == null || e.call(r), r = null;
81
+ }
82
+ D(s, (e) => {
83
+ e ? (r = document.activeElement, requestAnimationFrame(() => {
84
+ var n;
85
+ return (n = u.value) == null ? void 0 : n.focus();
86
+ }), document.addEventListener("keydown", v)) : document.removeEventListener("keydown", v);
87
+ });
88
+ function w(e) {
89
+ const n = e.detail;
90
+ n && typeof n == "object" && d(n);
91
+ }
92
+ async function $() {
93
+ const n = `${a.apiBase.replace(/\/$/, "")}/public/marketing-popup/config?organizationId=${encodeURIComponent(a.organizationId)}`;
94
+ try {
95
+ const o = await fetch(n);
96
+ if (!o.ok) {
97
+ d({ enabled: !1 });
98
+ return;
99
+ }
100
+ const p = await o.json();
101
+ d(p);
102
+ } catch {
103
+ d({ enabled: !1 });
104
+ }
105
+ }
106
+ return K(() => {
107
+ window.addEventListener(B, w), $();
108
+ }), N(() => {
109
+ window.removeEventListener(B, w), document.removeEventListener("keydown", v), l && clearTimeout(l);
110
+ }), (e, n) => {
111
+ var o;
112
+ return f(), R(A, { to: "body" }, [
113
+ s.value ? (f(), g("div", {
114
+ key: 0,
115
+ class: "reeka-popup",
116
+ role: "dialog",
117
+ "aria-modal": "true",
118
+ "aria-label": "Promotion",
119
+ onKeydown: P(C(m, ["prevent"]), ["escape"])
120
+ }, [
121
+ y("div", {
122
+ class: "reeka-popup__backdrop",
123
+ onClick: m
124
+ }),
125
+ y("div", {
126
+ ref_key: "dialogEl",
127
+ ref: u,
128
+ class: "reeka-popup__dialog",
129
+ tabindex: "-1",
130
+ onClick: n[0] || (n[0] = C(() => {
131
+ }, ["stop"]))
132
+ }, [
133
+ y("button", {
134
+ type: "button",
135
+ class: "reeka-popup__close",
136
+ "aria-label": "Close",
137
+ onClick: m
138
+ }, " × "),
139
+ (o = t.value) != null && o.imageUrl ? (f(), g("img", {
140
+ key: 0,
141
+ class: "reeka-popup__image",
142
+ src: t.value.imageUrl,
143
+ alt: ""
144
+ }, null, 8, F)) : b("", !0),
145
+ I.value ? (f(), g("a", {
146
+ key: 1,
147
+ class: "reeka-popup__cta",
148
+ href: t.value.ctaUrl,
149
+ target: t.value.openInNewTab ? "_blank" : void 0,
150
+ rel: t.value.openInNewTab ? "noopener noreferrer" : void 0,
151
+ onClick: L
152
+ }, V(t.value.ctaLabel), 9, x)) : b("", !0)
153
+ ], 512)
154
+ ], 40, q)) : b("", !0)
155
+ ]);
156
+ };
157
+ }
158
+ });
159
+ async function G(i) {
160
+ if (typeof window > "u" || !i.apiBase || !i.organizationId || document.getElementById("reeka-minisite-popup-root")) return null;
161
+ const a = document.createElement("div");
162
+ a.id = "reeka-minisite-popup-root", document.body.appendChild(a);
163
+ const t = j(W, {
164
+ apiBase: i.apiBase,
165
+ organizationId: i.organizationId,
166
+ skipBookingRoutes: i.skipBookingRoutes !== !1
167
+ });
168
+ return t.mount(a), t;
169
+ }
170
+ export {
171
+ G as installMinisitePopup
172
+ };
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .reeka-popup{position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:1rem;box-sizing:border-box}.reeka-popup__backdrop{position:absolute;top:0;right:0;bottom:0;left:0;background:#0f172a8c}.reeka-popup__dialog{position:relative;z-index:1;max-width:min(92vw,420px);max-height:min(88vh,720px);display:flex;flex-direction:column;align-items:stretch;gap:.75rem;outline:none}.reeka-popup__close{position:absolute;top:-.35rem;right:-.35rem;z-index:2;width:2rem;height:2rem;border:none;border-radius:999px;background:#fff;color:#111;font-size:1.35rem;line-height:1;cursor:pointer;box-shadow:0 1px 4px #0003}.reeka-popup__close:hover{background:#f3f4f6}.reeka-popup__image{display:block;width:100%;max-height:min(72vh,560px);object-fit:contain;border-radius:8px;background:#0f172a}.reeka-popup__cta{display:inline-flex;align-items:center;justify-content:center;align-self:center;min-width:10rem;padding:.65rem 1.25rem;border-radius:8px;background:#111827;color:#fff;font:600 .9rem/1.2 system-ui,-apple-system,sans-serif;text-decoration:none}.reeka-popup__cta:hover{background:#1f2937}
@@ -0,0 +1,18 @@
1
+ export type MarketingPopupConfig = {
2
+ enabled: boolean;
3
+ delayMs?: number;
4
+ imageUrl?: string;
5
+ ctaEnabled?: boolean;
6
+ ctaLabel?: string;
7
+ ctaUrl?: string;
8
+ openInNewTab?: boolean;
9
+ };
10
+ export type MinisitePopupInstallOptions = {
11
+ apiBase: string;
12
+ /** Org Mongo _id, publicKey, or agentListingId */
13
+ organizationId: string;
14
+ /**
15
+ * When true (default), skip showing on booking/checkout paths.
16
+ */
17
+ skipBookingRoutes?: boolean;
18
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@reeka/minisite-popup",
3
+ "version": "0.1.0",
4
+ "description": "Marketing image lightbox popup for Reeka mini-sites and agent listing sites",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "style": "./dist/style.css",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "vite build && tsc -p tsconfig.build.json --emitDeclarationOnly",
23
+ "dev": "vite build --watch",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "peerDependencies": {
27
+ "vue": "^3.5.0"
28
+ },
29
+ "devDependencies": {
30
+ "@vitejs/plugin-vue": "^5.1.4",
31
+ "typescript": "~5.6.2",
32
+ "vite": "^5.4.9",
33
+ "vue": "^3.5.12"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/ReekaTech/minisite-popup.git"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "registry": "https://registry.npmjs.org/"
42
+ },
43
+ "license": "SEE LICENSE IN LICENSE",
44
+ "private": false
45
+ }