@hobenakicoffee/libraries 3.4.1 → 4.0.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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -246
  3. package/package.json +22 -22
  4. package/src/App.tsx +192 -19
  5. package/src/index.css +0 -1
  6. package/src/utils/get-product-link.ts +1 -1
  7. package/src/components/turnstile-captcha.tsx +0 -47
  8. package/src/components/ui/button.tsx +0 -77
  9. package/src/components/ui/calendar.tsx +0 -235
  10. package/src/components/ui/spinner.tsx +0 -18
  11. package/src/constants/common.test.ts +0 -33
  12. package/src/constants/legal.test.ts +0 -72
  13. package/src/constants/payment.test.ts +0 -259
  14. package/src/constants/platforms.test.ts +0 -66
  15. package/src/constants/services.test.ts +0 -58
  16. package/src/lib/utils.ts +0 -6
  17. package/src/moderation/profanity-service.test.ts +0 -106
  18. package/src/providers/theme-provider.tsx +0 -73
  19. package/src/utils/check-moderation.test.ts +0 -321
  20. package/src/utils/format-amount.test.ts +0 -30
  21. package/src/utils/format-count.test.ts +0 -56
  22. package/src/utils/format-date.test.ts +0 -19
  23. package/src/utils/format-number.test.ts +0 -29
  24. package/src/utils/format-plain-text.test.ts +0 -36
  25. package/src/utils/get-newsletter-post-link.test.ts +0 -27
  26. package/src/utils/get-product-link.test.ts +0 -36
  27. package/src/utils/get-social-handle.test.ts +0 -32
  28. package/src/utils/get-social-link.test.ts +0 -63
  29. package/src/utils/get-user-name-initials.test.ts +0 -34
  30. package/src/utils/get-user-page-link.test.ts +0 -9
  31. package/src/utils/open-to-new-window.test.ts +0 -34
  32. package/src/utils/post-to-facebook.test.ts +0 -43
  33. package/src/utils/post-to-instagram.test.ts +0 -56
  34. package/src/utils/post-to-linkedin.test.ts +0 -43
  35. package/src/utils/post-to-x.test.ts +0 -45
  36. package/src/utils/qr-svg-utils.test.ts +0 -104
  37. package/src/utils/to-human-readable.test.ts +0 -25
  38. package/src/utils/validate-phone-number.test.ts +0 -28
@@ -1,9 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { getUserPageLink } from "./get-user-page-link";
3
-
4
- describe("getUserPageLink", () => {
5
- test("builds user page link with sanitized username", () => {
6
- const result = getUserPageLink(" @john doe ");
7
- expect(result.endsWith("/@%40johndoe")).toBe(true);
8
- });
9
- });
@@ -1,34 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { openInNewWindow } from "./open-to-new-window";
3
-
4
- describe("openInNewWindow", () => {
5
- const originalWindow = (globalThis as any).window;
6
-
7
- afterEach(() => {
8
- (globalThis as any).window = originalWindow;
9
- });
10
-
11
- test("opens url in a new tab", () => {
12
- const calls: unknown[][] = [];
13
- (globalThis as any).window = {
14
- open: (...args: unknown[]) => {
15
- calls.push(args);
16
- },
17
- };
18
-
19
- openInNewWindow("https://example.com");
20
-
21
- expect(calls).toHaveLength(1);
22
- expect(calls[0]).toEqual([
23
- "https://example.com",
24
- "_blank",
25
- "noopener,noreferrer",
26
- ]);
27
- });
28
-
29
- test("does nothing when window is undefined", () => {
30
- (globalThis as any).window = undefined;
31
-
32
- expect(() => openInNewWindow("https://example.com")).not.toThrow();
33
- });
34
- });
@@ -1,43 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { shareToFacebook } from "./post-to-facebook";
3
-
4
- describe("shareToFacebook", () => {
5
- const originalWindow = (globalThis as any).window;
6
-
7
- afterEach(() => {
8
- (globalThis as any).window = originalWindow;
9
- });
10
-
11
- test("throws when url is missing", () => {
12
- expect(() => shareToFacebook({ url: "" })).toThrow(
13
- "Facebook share requires a URL"
14
- );
15
- });
16
-
17
- test("opens facebook share url with params", () => {
18
- const calls: unknown[][] = [];
19
- (globalThis as any).window = {
20
- open: (...args: unknown[]) => {
21
- calls.push(args);
22
- },
23
- };
24
-
25
- shareToFacebook({
26
- url: "https://example.com",
27
- quote: "Hello",
28
- hashtag: "#coffee",
29
- ref: "campaign",
30
- });
31
-
32
- const params = new URLSearchParams({ u: "https://example.com" });
33
- params.append("quote", "Hello");
34
- params.append("hashtag", "#coffee");
35
- params.append("ref", "campaign");
36
-
37
- expect(calls[0]).toEqual([
38
- `https://www.facebook.com/sharer/sharer.php?${params.toString()}`,
39
- "_blank",
40
- "noopener,noreferrer",
41
- ]);
42
- });
43
- });
@@ -1,56 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- mock.module("sonner", () => ({
4
- toast: {
5
- success: () => undefined,
6
- error: () => undefined,
7
- },
8
- }));
9
-
10
- describe("shareToInstagram", () => {
11
- const originalNavigator = (globalThis as any).navigator;
12
- const originalWindow = (globalThis as any).window;
13
-
14
- beforeEach(() => {
15
- (globalThis as any).window = {
16
- open: () => undefined,
17
- };
18
- });
19
-
20
- afterEach(() => {
21
- (globalThis as any).navigator = originalNavigator;
22
- (globalThis as any).window = originalWindow;
23
- });
24
-
25
- test("throws when url is missing", async () => {
26
- const { shareToInstagram } = await import("./post-to-instagram");
27
-
28
- expect(() => shareToInstagram({ url: "" })).toThrow(
29
- "Instagram share requires a URL"
30
- );
31
- });
32
-
33
- test("uses Web Share API when available", async () => {
34
- const shareCalls: unknown[] = [];
35
-
36
- (globalThis as any).navigator = {
37
- share: (payload: unknown) => {
38
- shareCalls.push(payload);
39
- return Promise.resolve();
40
- },
41
- clipboard: {
42
- writeText: () => Promise.resolve(),
43
- },
44
- };
45
-
46
- const { shareToInstagram } = await import("./post-to-instagram");
47
-
48
- shareToInstagram({ url: "https://example.com", text: "Look" });
49
-
50
- expect(shareCalls).toHaveLength(1);
51
- expect(shareCalls[0]).toEqual({
52
- title: "Look",
53
- url: "https://example.com",
54
- });
55
- });
56
- });
@@ -1,43 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { shareToLinkedIn } from "./post-to-linkedin";
3
-
4
- describe("shareToLinkedIn", () => {
5
- const originalWindow = (globalThis as any).window;
6
-
7
- afterEach(() => {
8
- (globalThis as any).window = originalWindow;
9
- });
10
-
11
- test("throws when url is missing", () => {
12
- expect(() => shareToLinkedIn({ url: "" })).toThrow(
13
- "LinkedIn share requires a URL"
14
- );
15
- });
16
-
17
- test("opens linkedin share url with params", () => {
18
- const calls: unknown[][] = [];
19
- (globalThis as any).window = {
20
- open: (...args: unknown[]) => {
21
- calls.push(args);
22
- },
23
- };
24
-
25
- shareToLinkedIn({
26
- url: "https://example.com",
27
- title: "Title",
28
- summary: "Summary",
29
- source: "Source",
30
- });
31
-
32
- const params = new URLSearchParams({ url: "https://example.com" });
33
- params.append("title", "Title");
34
- params.append("summary", "Summary");
35
- params.append("source", "Source");
36
-
37
- expect(calls[0]).toEqual([
38
- `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`,
39
- "_blank",
40
- "noopener,noreferrer",
41
- ]);
42
- });
43
- });
@@ -1,45 +0,0 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { shareToX } from "./post-to-x";
3
-
4
- describe("shareToX", () => {
5
- const originalWindow = (globalThis as any).window;
6
-
7
- afterEach(() => {
8
- (globalThis as any).window = originalWindow;
9
- });
10
-
11
- test("throws when text is missing", () => {
12
- expect(() => shareToX({ text: "" })).toThrow(
13
- "X share requires text content"
14
- );
15
- });
16
-
17
- test("opens x intent url with params", () => {
18
- const calls: unknown[][] = [];
19
- (globalThis as any).window = {
20
- open: (...args: unknown[]) => {
21
- calls.push(args);
22
- },
23
- };
24
-
25
- shareToX({
26
- text: "Hello",
27
- url: "https://example.com",
28
- hashtags: "coffee,shop",
29
- via: "hobenaki",
30
- related: "friend1,friend2",
31
- });
32
-
33
- const params = new URLSearchParams({ text: "Hello" });
34
- params.append("url", "https://example.com");
35
- params.append("hashtags", "coffee,shop");
36
- params.append("via", "hobenaki");
37
- params.append("related", "friend1,friend2");
38
-
39
- expect(calls[0]).toEqual([
40
- `https://twitter.com/intent/tweet?${params.toString()}`,
41
- "_blank",
42
- "noopener,noreferrer",
43
- ]);
44
- });
45
- });
@@ -1,104 +0,0 @@
1
- import { afterEach, describe, expect, mock, test } from "bun:test";
2
-
3
- mock.module("sonner", () => ({
4
- toast: {
5
- success: () => undefined,
6
- error: () => undefined,
7
- },
8
- }));
9
-
10
- describe("downloadQrSvgAsPng", () => {
11
- const originalImage = (globalThis as any).Image;
12
-
13
- afterEach(() => {
14
- (globalThis as any).Image = originalImage;
15
- });
16
-
17
- test("calls onError when image loading fails", async () => {
18
- class BrokenImage {
19
- public onload: (() => void) | null = null;
20
- public onerror: (() => void) | null = null;
21
- public decoding = "async";
22
-
23
- set src(_value: string) {
24
- this.onerror?.();
25
- }
26
- }
27
-
28
- (globalThis as any).Image = BrokenImage;
29
-
30
- const { downloadQrSvgAsPng } = await import("./qr-svg-utils");
31
-
32
- let failed = false;
33
- await downloadQrSvgAsPng("<svg></svg>", "test.png", undefined, () => {
34
- failed = true;
35
- });
36
-
37
- expect(failed).toBe(true);
38
- });
39
- });
40
-
41
- describe("printQrSvg", () => {
42
- const originalDocument = (globalThis as any).document;
43
- const originalDOMParser = (globalThis as any).DOMParser;
44
-
45
- afterEach(() => {
46
- (globalThis as any).document = originalDocument;
47
- (globalThis as any).DOMParser = originalDOMParser;
48
- });
49
-
50
- test("calls onError for invalid SVG markup", async () => {
51
- const createNode = () => ({
52
- style: {},
53
- textContent: "",
54
- setAttribute: () => undefined,
55
- append: () => undefined,
56
- appendChild: () => undefined,
57
- });
58
-
59
- const iframeDocument = {
60
- documentElement: {},
61
- createElement: () => createNode(),
62
- replaceChild: () => undefined,
63
- };
64
-
65
- const iframe = {
66
- style: {},
67
- contentWindow: {
68
- document: iframeDocument,
69
- addEventListener: () => undefined,
70
- focus: () => undefined,
71
- print: () => undefined,
72
- },
73
- setAttribute: () => undefined,
74
- remove: () => undefined,
75
- };
76
-
77
- (globalThis as any).document = {
78
- createElement: (tagName: string) =>
79
- tagName === "iframe" ? iframe : createNode(),
80
- body: {
81
- append: () => undefined,
82
- },
83
- };
84
-
85
- (globalThis as any).DOMParser = class {
86
- parseFromString() {
87
- return {
88
- documentElement: {
89
- nodeName: "parsererror",
90
- },
91
- };
92
- }
93
- };
94
-
95
- const { printQrSvg } = await import("./qr-svg-utils");
96
-
97
- let failed = false;
98
- printQrSvg("<svg", "QR Print", () => {
99
- failed = true;
100
- });
101
-
102
- expect(failed).toBe(true);
103
- });
104
- });
@@ -1,25 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { toHumanReadable } from "./to-human-readable";
3
-
4
- describe("toHumanReadable", () => {
5
- test("returns empty string for blank input", () => {
6
- expect(toHumanReadable(" ")).toBe("");
7
- });
8
-
9
- test("normalizes separators and trims", () => {
10
- expect(toHumanReadable(" hello-world__again ")).toBe("Hello World Again");
11
- });
12
-
13
- test("splits camelCase and title cases words", () => {
14
- expect(toHumanReadable("supporterName")).toBe("Supporter Name");
15
- });
16
-
17
- test("preserves uppercase acronyms and numbers", () => {
18
- expect(toHumanReadable("APIResponseV2")).toBe("API Response V2");
19
- expect(toHumanReadable("USER_ID_2")).toBe("USER ID 2");
20
- });
21
-
22
- test("handles mixed separators and spacing", () => {
23
- expect(toHumanReadable("foo__BAR-baz QUX")).toBe("Foo BAR Baz QUX");
24
- });
25
- });
@@ -1,28 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { validatePhoneNumber } from "./validate-phone-number";
3
-
4
- describe("validatePhoneNumber", () => {
5
- test("returns true for valid Bangladeshi mobile numbers", () => {
6
- expect(validatePhoneNumber("01712345678")).toBe(true);
7
- expect(validatePhoneNumber("+8801712345678")).toBe(true);
8
- expect(validatePhoneNumber("8801712345678")).toBe(true);
9
- });
10
-
11
- test("normalizes spaces and dashes before validation", () => {
12
- expect(validatePhoneNumber("01 7123-45678")).toBe(true);
13
- });
14
-
15
- test("returns false for invalid operator prefix", () => {
16
- expect(validatePhoneNumber("01212345678")).toBe(false);
17
- expect(validatePhoneNumber("+8801212345678")).toBe(false);
18
- });
19
-
20
- test("returns false for invalid length", () => {
21
- expect(validatePhoneNumber("0171234567")).toBe(false);
22
- expect(validatePhoneNumber("017123456789")).toBe(false);
23
- });
24
-
25
- test("returns false for non-numeric input", () => {
26
- expect(validatePhoneNumber("abc01712345678")).toBe(false);
27
- });
28
- });