@networkpro/web 1.10.1 → 1.12.2

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.
@@ -9,41 +9,33 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import { base } from "$app/paths";
11
11
  import { onMount } from "svelte";
12
- import { getTrackingPreferences } from "$lib/utils/trackingStatus.js";
13
- /** @type {(type: 'enable' | 'disable') => void} */
14
12
  import {
15
- setTrackingPreference,
16
- clearTrackingPreferences,
17
- } from "$lib/utils/trackingCookies.js";
13
+ trackingPreferences,
14
+ setOptIn,
15
+ setOptOut,
16
+ clearPrefs,
17
+ } from "$lib/stores/trackingPreferences.js";
18
18
  import { CONSTANTS } from "$lib";
19
19
 
20
- // Log the base path to verify its value
21
- //console.log("Base path:", base);
22
-
23
- console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
24
-
25
20
  const { COMPANY_INFO, CONTACT, PAGE, NAV } = CONSTANTS;
26
21
 
27
- /**
28
- * URL to the Privacy Rights Request Form redirect route, using the base path
29
- * URL to the Contact Form redirect route, using the base path
30
- * URL to the Privacy Dashboard using the base path
31
- * @type {string}
32
- */
22
+ /** @type {string} */
33
23
  const prightsLink = `${base}/privacy-rights`;
24
+
25
+ /** @type {string} */
34
26
  const contactLink = `${base}/contact`;
27
+
28
+ /** @type {string} */
35
29
  const pdashLink = `${base}/privacy-dashboard`;
36
30
 
37
- /**
38
- * URL to the privacy policy in Markdown format
39
- * External URL to the GPC website
40
- * @type {string}
41
- */
31
+ /** @type {string} */
42
32
  const privacyLink = "https://docs.netwk.pro/privacy";
33
+
34
+ /** @type {string} */
43
35
  const gpcLink = "https://globalprivacycontrol.org/";
44
36
 
45
37
  /**
46
- * Table of Contents Links
38
+ * Table of Contents sections and their headings.
47
39
  * @type {{ id: string, text: string }[]}
48
40
  */
49
41
  const tocLinks = [
@@ -67,47 +59,49 @@ This file is part of Network Pro.
67
59
  /** @type {string} */
68
60
  const classSmall = "small-text";
69
61
 
70
- let optedOut = false;
71
- let optedIn = false;
72
- let trackingStatus = "";
62
+ /** @type {boolean} */
63
+ let optedOut;
73
64
 
74
- onMount(() => {
75
- const prefs = getTrackingPreferences();
76
- optedOut = prefs.optedOut;
77
- optedIn = prefs.optedIn;
78
- trackingStatus = prefs.status;
79
- console.log("[Tracking] Status:", trackingStatus);
80
- });
65
+ /** @type {boolean} */
66
+ let optedIn;
67
+
68
+ /** @type {string} */
69
+ let trackingStatus;
70
+
71
+ // Reactive assignments from the store
72
+ $: ({ optedOut, optedIn, status: trackingStatus } = $trackingPreferences);
81
73
 
82
74
  /**
83
- * Toggle tracking opt-out.
75
+ * Toggle tracking opt-out setting.
84
76
  * @param {boolean} value
85
77
  */
86
78
  function toggleTracking(value) {
87
- optedOut = value;
88
- if (optedOut) {
79
+ if (value) {
89
80
  console.log("[Tracking] User opted out");
90
- setTrackingPreference("disable");
81
+ setOptOut();
91
82
  } else {
92
83
  console.log("[Tracking] User cleared opt-out");
93
- clearTrackingPreferences();
84
+ clearPrefs();
94
85
  }
95
86
  }
96
87
 
97
88
  /**
98
- * Toggle tracking opt-in.
89
+ * Toggle tracking opt-in setting.
99
90
  * @param {boolean} value
100
91
  */
101
92
  function toggleOptIn(value) {
102
- optedIn = value;
103
- if (optedIn) {
93
+ if (value) {
104
94
  console.log("[Tracking] User opted in");
105
- setTrackingPreference("enable");
95
+ setOptIn();
106
96
  } else {
107
97
  console.log("[Tracking] User cleared opt-in");
108
- clearTrackingPreferences();
98
+ clearPrefs();
109
99
  }
110
100
  }
101
+
102
+ onMount(() => {
103
+ console.log("[Tracking] Store initialized:", $trackingPreferences.status);
104
+ });
111
105
  </script>
112
106
 
113
107
  <!-- BEGIN TITLE -->
@@ -241,38 +235,51 @@ This file is part of Network Pro.
241
235
  <div class="spacer"></div>
242
236
 
243
237
  <h3>Tracking Preferences</h3>
244
- <p id="tracking-status" aria-live="polite">
245
- <strong>Tracking Status:</strong>
246
- {trackingStatus}
247
- </p>
248
238
 
249
- <!-- Opt-out checkbox -->
250
- <label>
251
- <input
252
- type="checkbox"
253
- checked={optedOut}
254
- disabled={optedIn}
255
- aria-describedby="tracking-status"
256
- on:change={(e) =>
257
- toggleTracking(
258
- /** @type {HTMLInputElement} */ (e.target).checked,
259
- )} />
260
- <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
261
- </label>
262
-
263
- <br />
264
-
265
- <!-- Opt-in checkbox -->
266
- <label>
267
- <input
268
- type="checkbox"
269
- checked={optedIn}
270
- disabled={optedOut}
271
- aria-describedby="tracking-status"
272
- on:change={(e) =>
273
- toggleOptIn(/** @type {HTMLInputElement} */ (e.target).checked)} />
274
- <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
275
- </label>
239
+ {#if trackingStatus && trackingStatus !== "⏳ Checking tracking preferences..."}
240
+ <p id="tracking-status" aria-live="polite">
241
+ <strong>Tracking Status:</strong>
242
+ {trackingStatus}
243
+ </p>
244
+ {:else}
245
+ <p id="tracking-status" aria-live="polite">
246
+ <strong>Tracking Status:</strong> <em>Loading…</em>
247
+ </p>
248
+ {/if}
249
+
250
+ <fieldset>
251
+ <legend class="sr-only">Tracking Preference Controls</legend>
252
+
253
+ <!-- Opt-out checkbox -->
254
+ <label>
255
+ <input
256
+ type="checkbox"
257
+ checked={optedOut}
258
+ disabled={optedIn}
259
+ aria-describedby="tracking-status"
260
+ on:change={(e) =>
261
+ toggleTracking(
262
+ /** @type {HTMLInputElement} */ (e.target).checked,
263
+ )} />
264
+ <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
265
+ </label>
266
+
267
+ <br />
268
+
269
+ <!-- Opt-in checkbox -->
270
+ <label>
271
+ <input
272
+ type="checkbox"
273
+ checked={optedIn}
274
+ disabled={optedOut}
275
+ aria-describedby="tracking-status"
276
+ on:change={(e) =>
277
+ toggleOptIn(
278
+ /** @type {HTMLInputElement} */ (e.target).checked,
279
+ )} />
280
+ <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
281
+ </label>
282
+ </fieldset>
276
283
 
277
284
  <div class="spacer"></div>
278
285
 
@@ -405,7 +412,7 @@ This file is part of Network Pro.
405
412
  For questions, please utilize our <a
406
413
  rel={PAGE.REL}
407
414
  href={contactLink}
408
- target={PAGE.SELF}>Contact Form</a> or contact us directly:
415
+ target={PAGE.BLANK}>Contact Form</a> or contact us directly:
409
416
  </p>
410
417
  <p>
411
418
  <strong>{COMPANY_INFO.NAME}</strong><br />
@@ -9,78 +9,76 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import { base } from "$app/paths";
11
11
  import { onMount } from "svelte";
12
- import { getTrackingPreferences } from "$lib/utils/trackingStatus.js";
13
- /** @type {(type: 'enable' | 'disable') => void} */
14
- import {
15
- setTrackingPreference,
16
- clearTrackingPreferences,
17
- } from "$lib/utils/trackingCookies.js";
18
- import { CONSTANTS } from "$lib";
19
12
 
20
- // Log the base path to verify its value
21
- //console.log("Base path:", base);
13
+ import {
14
+ trackingPreferences,
15
+ setOptIn,
16
+ setOptOut,
17
+ clearPrefs,
18
+ } from "$lib/stores/trackingPreferences.js";
22
19
 
23
- console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
20
+ import { CONSTANTS } from "$lib";
24
21
 
25
22
  const { CONTACT, PAGE, NAV } = CONSTANTS;
26
23
 
27
- /**
28
- * @type {string}
29
- * Style class for the div element.
30
- */
31
- const spaceStyle = "spacer";
32
-
33
- /**
34
- * URL to the full Privacy Policy using the base path
35
- * @type {string}
36
- */
24
+ /** @type {string} */
37
25
  const privacyPolicy = `${base}/privacy`;
26
+
27
+ /** @type {string} */
38
28
  const prightsLink = `${base}/privacy-rights`;
39
29
 
40
30
  /** @type {string} */
41
31
  const classSmall = "small-text";
42
32
 
43
- let optedOut = false;
44
- let optedIn = false;
45
- let trackingStatus = "";
33
+ /** @type {string} */
34
+ const spaceStyle = "spacer";
46
35
 
47
- onMount(() => {
48
- const prefs = getTrackingPreferences();
49
- optedOut = prefs.optedOut;
50
- optedIn = prefs.optedIn;
51
- trackingStatus = prefs.status;
52
- console.log("[Tracking] Status:", trackingStatus);
53
- });
36
+ /** @type {boolean} */
37
+ let optedOut;
38
+
39
+ /** @type {boolean} */
40
+ let optedIn;
41
+
42
+ /** @type {string} */
43
+ let trackingStatus;
44
+
45
+ // Reactive assignments from the store
46
+ $: ({ optedOut, optedIn, status: trackingStatus } = $trackingPreferences);
54
47
 
55
48
  /**
56
- * Toggle tracking opt-out.
49
+ * Toggle tracking opt-out setting.
57
50
  * @param {boolean} value
58
51
  */
59
52
  function toggleTracking(value) {
60
- optedOut = value;
61
- if (optedOut) {
53
+ if (value) {
62
54
  console.log("[Tracking] User opted out");
63
- setTrackingPreference("disable");
55
+ setOptOut();
64
56
  } else {
65
57
  console.log("[Tracking] User cleared opt-out");
66
- clearTrackingPreferences();
58
+ clearPrefs();
67
59
  }
68
60
  }
69
61
 
70
62
  /**
71
- * Toggle tracking opt-in.
63
+ * Toggle tracking opt-in setting.
72
64
  * @param {boolean} value
73
65
  */
74
66
  function toggleOptIn(value) {
75
- optedIn = value;
76
- if (optedIn) {
67
+ if (value) {
77
68
  console.log("[Tracking] User opted in");
78
- setTrackingPreference("enable");
69
+ setOptIn();
79
70
  } else {
80
71
  console.log("[Tracking] User cleared opt-in");
81
- clearTrackingPreferences();
72
+ clearPrefs();
82
73
  }
83
74
  }
75
+
76
+ onMount(() => {
77
+ console.log(
78
+ "[PrivacyDashboard] Tracking status:",
79
+ $trackingPreferences.status,
80
+ );
81
+ });
84
82
  </script>
85
83
 
86
84
  <section id="top">
@@ -143,36 +141,46 @@ This file is part of Network Pro.
143
141
 
144
142
  &nbsp;
145
143
 
146
- <p id="tracking-status" aria-live="polite">
147
- <strong>Tracking Status:</strong>
148
- {trackingStatus}
149
- </p>
150
-
151
- <!-- Opt-out checkbox -->
152
- <label>
153
- <input
154
- type="checkbox"
155
- checked={optedOut}
156
- disabled={optedIn}
157
- aria-describedby="tracking-status"
158
- on:change={(e) =>
159
- toggleTracking(/** @type {HTMLInputElement} */ (e.target).checked)} />
160
- <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
161
- </label>
162
-
163
- <br />
164
-
165
- <!-- Opt-in checkbox -->
166
- <label>
167
- <input
168
- type="checkbox"
169
- checked={optedIn}
170
- disabled={optedOut}
171
- aria-describedby="tracking-status"
172
- on:change={(e) =>
173
- toggleOptIn(/** @type {HTMLInputElement} */ (e.target).checked)} />
174
- <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
175
- </label>
144
+ {#if trackingStatus && trackingStatus !== "⏳ Checking tracking preferences..."}
145
+ <p id="tracking-status" aria-live="polite">
146
+ <strong>Tracking Status:</strong>
147
+ {trackingStatus}
148
+ </p>
149
+ {:else}
150
+ <p id="tracking-status" aria-live="polite">
151
+ <strong>Tracking Status:</strong> <em>Loading…</em>
152
+ </p>
153
+ {/if}
154
+
155
+ <fieldset>
156
+ <legend class="sr-only">Tracking Preference Controls</legend>
157
+
158
+ <!-- Opt-out checkbox -->
159
+ <label>
160
+ <input
161
+ type="checkbox"
162
+ checked={optedOut}
163
+ disabled={optedIn}
164
+ aria-describedby="tracking-status"
165
+ on:change={(e) =>
166
+ toggleTracking(/** @type {HTMLInputElement} */ (e.target).checked)} />
167
+ <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
168
+ </label>
169
+
170
+ <br />
171
+
172
+ <!-- Opt-in checkbox -->
173
+ <label>
174
+ <input
175
+ type="checkbox"
176
+ checked={optedIn}
177
+ disabled={optedOut}
178
+ aria-describedby="tracking-status"
179
+ on:change={(e) =>
180
+ toggleOptIn(/** @type {HTMLInputElement} */ (e.target).checked)} />
181
+ <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
182
+ </label>
183
+ </fieldset>
176
184
 
177
185
  <div class={spaceStyle}></div>
178
186
 
@@ -0,0 +1,115 @@
1
+ /* ==========================================================================
2
+ src/lib/stores/posthog.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ ========================================================================== */
7
+
8
+ /**
9
+ * @file posthog.js
10
+ * @description Privacy-aware PostHog tracking store with reactive state and safe API surface.
11
+ * @module src/lib/stores
12
+ */
13
+
14
+ import {
15
+ remindUserToReconsent,
16
+ trackingPreferences,
17
+ } from "$lib/stores/trackingPreferences.js";
18
+ import { get, writable } from "svelte/store";
19
+
20
+ /**
21
+ * Tracks whether tracking is allowed based on cookies, DNT/GPC, and user preference.
22
+ * @type {import("svelte/store").Writable<boolean>}
23
+ */
24
+ export const trackingEnabled = writable(false);
25
+
26
+ /**
27
+ * Determines if the user should be reminded to re-consent (after 6 months).
28
+ * @type {import("svelte/store").Writable<boolean>}
29
+ */
30
+ export const showReminder = writable(false);
31
+
32
+ /** @type {boolean} Internal one-time init guard */
33
+ let initialized = false;
34
+
35
+ /** @type {import("posthog-js").PostHog | null} Loaded PostHog instance */
36
+ let ph = null;
37
+
38
+ /**
39
+ * Initializes the PostHog analytics client if tracking is permitted.
40
+ * Uses dynamic import to avoid SSR failures.
41
+ * @returns {Promise<void>}
42
+ */
43
+ export async function initPostHog() {
44
+ if (initialized || typeof window === "undefined") return;
45
+ initialized = true;
46
+
47
+ const { enabled } = get(trackingPreferences);
48
+ trackingEnabled.set(enabled);
49
+ showReminder.set(get(remindUserToReconsent)); // ✅ use derived store instead
50
+
51
+ if (!enabled) {
52
+ console.log("[PostHog] Tracking is disabled — skipping init.");
53
+ return;
54
+ }
55
+
56
+ const posthogModule = await import("posthog-js");
57
+ ph = posthogModule.default;
58
+
59
+ // cspell:disable-next-line
60
+ ph.init("phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0", {
61
+ api_host: "https://us.i.posthog.com",
62
+ autocapture: true,
63
+ capture_pageview: false,
64
+ person_profiles: "identified_only",
65
+ loaded: (phInstance) => {
66
+ if (!enabled) {
67
+ console.log(
68
+ "[PostHog] ⛔ User opted out — calling opt_out_capturing()",
69
+ );
70
+ phInstance.opt_out_capturing();
71
+ } else {
72
+ console.log("[PostHog] ✅ Tracking enabled");
73
+ }
74
+ },
75
+ });
76
+
77
+ ph.capture("$pageview");
78
+ }
79
+
80
+ /**
81
+ * Conditionally captures an event if tracking is enabled.
82
+ * @param {string} event - The event name to track
83
+ * @param {Record<string, any>} [properties={}] - Optional event properties
84
+ */
85
+ export function capture(event, properties = {}) {
86
+ if (ph !== null && get(trackingEnabled)) {
87
+ ph.capture(event, properties);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Conditionally identifies a user if tracking is enabled.
93
+ * @param {string} id - Unique user identifier
94
+ * @param {Record<string, any>} [properties={}] - Optional user traits
95
+ */
96
+ export function identify(id, properties = {}) {
97
+ if (ph !== null && get(trackingEnabled)) {
98
+ ph.identify(id, properties);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * For test cleanup only — resets internal state.
104
+ * No-op in production.
105
+ * @returns {void}
106
+ */
107
+ export function _resetPostHog() {
108
+ if (import.meta.env.MODE === "production") {
109
+ console.warn("[PostHog] _resetPostHog() called in production — no-op");
110
+ return;
111
+ }
112
+
113
+ initialized = false;
114
+ ph = null;
115
+ }