@hobenakicoffee/libraries 1.11.0 → 1.13.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 (76) hide show
  1. package/README.md +25 -4
  2. package/package.json +84 -9
  3. package/src/App.tsx +28 -0
  4. package/src/components/turnstile-captcha.tsx +47 -0
  5. package/src/components/ui/alert-dialog.tsx +196 -0
  6. package/src/components/ui/alert.tsx +76 -0
  7. package/src/components/ui/avatar.tsx +110 -0
  8. package/src/components/ui/badge.tsx +49 -0
  9. package/src/components/ui/breadcrumb.tsx +122 -0
  10. package/src/components/ui/button-group.tsx +82 -0
  11. package/src/components/ui/button.tsx +77 -0
  12. package/src/components/ui/calendar.tsx +235 -0
  13. package/src/components/ui/card.tsx +100 -0
  14. package/src/components/ui/chart.tsx +364 -0
  15. package/src/components/ui/checkbox.tsx +30 -0
  16. package/src/components/ui/dialog.tsx +162 -0
  17. package/src/components/ui/drawer.tsx +126 -0
  18. package/src/components/ui/dropdown-menu.tsx +267 -0
  19. package/src/components/ui/empty-minimal.tsx +20 -0
  20. package/src/components/ui/empty.tsx +101 -0
  21. package/src/components/ui/field.tsx +235 -0
  22. package/src/components/ui/input-group.tsx +170 -0
  23. package/src/components/ui/input-otp.tsx +84 -0
  24. package/src/components/ui/input.tsx +37 -0
  25. package/src/components/ui/item.tsx +196 -0
  26. package/src/components/ui/label.tsx +19 -0
  27. package/src/components/ui/popover.tsx +87 -0
  28. package/src/components/ui/radio-group.tsx +47 -0
  29. package/src/components/ui/select.tsx +205 -0
  30. package/src/components/ui/separator.tsx +26 -0
  31. package/src/components/ui/sheet.tsx +141 -0
  32. package/src/components/ui/sidebar.tsx +699 -0
  33. package/src/components/ui/skeleton.tsx +13 -0
  34. package/src/components/ui/sonner.tsx +74 -0
  35. package/src/components/ui/spinner.tsx +18 -0
  36. package/src/components/ui/table.tsx +114 -0
  37. package/src/components/ui/tabs.tsx +88 -0
  38. package/src/components/ui/textarea.tsx +35 -0
  39. package/src/components/ui/toggle-group.tsx +91 -0
  40. package/src/components/ui/toggle.tsx +44 -0
  41. package/src/components/ui/tooltip.tsx +59 -0
  42. package/src/constants/common.test.ts +1 -1
  43. package/src/constants/legal.test.ts +1 -1
  44. package/src/constants/payment.test.ts +9 -9
  45. package/src/constants/platforms.test.ts +1 -1
  46. package/src/constants/services.test.ts +1 -1
  47. package/src/hooks/use-mobile.ts +19 -0
  48. package/src/index.css +135 -0
  49. package/src/lib/utils.ts +6 -0
  50. package/src/main.tsx +16 -0
  51. package/src/moderation/datasets/bn.ts +708 -708
  52. package/src/moderation/normalizer.test.ts +1 -1
  53. package/src/moderation/normalizer.ts +16 -16
  54. package/src/moderation/profanity-service.test.ts +3 -3
  55. package/src/providers/theme-provider.tsx +73 -0
  56. package/src/types/supabase.ts +751 -647
  57. package/src/utils/check-moderation.test.ts +12 -12
  58. package/src/utils/format-number.test.ts +1 -1
  59. package/src/utils/format-plain-text.test.ts +1 -1
  60. package/src/utils/get-social-handle.test.ts +3 -3
  61. package/src/utils/get-social-link.test.ts +9 -9
  62. package/src/utils/get-social-link.ts +5 -3
  63. package/src/utils/get-user-name-initials.test.ts +1 -1
  64. package/src/utils/get-user-name-initials.ts +4 -4
  65. package/src/utils/get-user-page-link.ts +1 -1
  66. package/src/utils/index.ts +5 -5
  67. package/src/utils/open-to-new-window.ts +3 -1
  68. package/src/utils/post-to-facebook.test.ts +1 -1
  69. package/src/utils/post-to-facebook.ts +9 -3
  70. package/src/utils/post-to-instagram.test.ts +1 -1
  71. package/src/utils/post-to-linkedin.test.ts +1 -1
  72. package/src/utils/post-to-linkedin.ts +9 -3
  73. package/src/utils/post-to-x.test.ts +1 -1
  74. package/src/utils/post-to-x.ts +12 -4
  75. package/src/utils/to-human-readable.ts +6 -2
  76. package/src/utils/validate-phone-number.test.ts +1 -1
@@ -1,6 +1,6 @@
1
- import { describe, expect, test, mock, beforeEach } from "bun:test";
2
- import { checkModeration } from "./check-moderation";
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
3
2
  import type OpenAI from "openai";
3
+ import { checkModeration } from "./check-moderation";
4
4
 
5
5
  describe("checkModeration", () => {
6
6
  let mockOpenAiClient: OpenAI;
@@ -8,7 +8,7 @@ describe("checkModeration", () => {
8
8
 
9
9
  beforeEach(() => {
10
10
  mockModerations = {
11
- create: mock(async (params: any) => ({
11
+ create: mock(async () => ({
12
12
  results: [
13
13
  {
14
14
  flagged: false,
@@ -63,7 +63,7 @@ describe("checkModeration", () => {
63
63
  });
64
64
 
65
65
  test("calls OpenAI moderations API with correct model", async () => {
66
- mockModerations.create = mock(async (params: any) => ({
66
+ mockModerations.create = mock(async () => ({
67
67
  results: [
68
68
  {
69
69
  flagged: false,
@@ -83,7 +83,7 @@ describe("checkModeration", () => {
83
83
 
84
84
  test("returns error object when OpenAI API throws", async () => {
85
85
  const testError = new Error("API Error");
86
- mockModerations.create = mock(async () => {
86
+ mockModerations.create = mock(() => {
87
87
  throw testError;
88
88
  });
89
89
 
@@ -97,7 +97,7 @@ describe("checkModeration", () => {
97
97
 
98
98
  test("returns error with stack trace when API fails", async () => {
99
99
  const testError = new Error("Network error");
100
- mockModerations.create = mock(async () => {
100
+ mockModerations.create = mock(() => {
101
101
  throw testError;
102
102
  });
103
103
 
@@ -126,7 +126,7 @@ describe("checkModeration", () => {
126
126
  });
127
127
 
128
128
  test("returns proper result structure for error case", async () => {
129
- mockModerations.create = mock(async () => {
129
+ mockModerations.create = mock(() => {
130
130
  throw new Error("Test error");
131
131
  });
132
132
 
@@ -208,7 +208,7 @@ describe("checkModeration", () => {
208
208
  ],
209
209
  }));
210
210
 
211
- const longText = "test ".repeat(10000);
211
+ const longText = "test ".repeat(10_000);
212
212
  const result = await checkModeration(mockOpenAiClient, longText);
213
213
 
214
214
  expect(result.source).toBe("openai");
@@ -216,7 +216,7 @@ describe("checkModeration", () => {
216
216
  });
217
217
 
218
218
  test("returns source as null on error", async () => {
219
- mockModerations.create = mock(async () => {
219
+ mockModerations.create = mock(() => {
220
220
  throw new Error("API Error");
221
221
  });
222
222
 
@@ -226,7 +226,7 @@ describe("checkModeration", () => {
226
226
  });
227
227
 
228
228
  test("returns flagged as false when error occurs", async () => {
229
- mockModerations.create = mock(async () => {
229
+ mockModerations.create = mock(() => {
230
230
  throw new Error("API Error");
231
231
  });
232
232
 
@@ -259,7 +259,7 @@ describe("checkModeration", () => {
259
259
  });
260
260
 
261
261
  test("passes input to OpenAI exactly as provided", async () => {
262
- mockModerations.create = mock(async (params: any) => ({
262
+ mockModerations.create = mock(async () => ({
263
263
  results: [
264
264
  {
265
265
  flagged: false,
@@ -277,7 +277,7 @@ describe("checkModeration", () => {
277
277
 
278
278
  test("preserves error details in result", async () => {
279
279
  const errorMessage = "Specific API Error";
280
- mockModerations.create = mock(async () => {
280
+ mockModerations.create = mock(() => {
281
281
  throw new Error(errorMessage);
282
282
  });
283
283
 
@@ -24,6 +24,6 @@ describe("formatNumber", () => {
24
24
  });
25
25
 
26
26
  test("formats large numbers", () => {
27
- expect(digitsFromFormatted(formatNumber(1000000))).toBe(1000000);
27
+ expect(digitsFromFormatted(formatNumber(1_000_000))).toBe(1_000_000);
28
28
  });
29
29
  });
@@ -23,7 +23,7 @@ describe("formatToPlainText", () => {
23
23
  test("stringifies objects and arrays", () => {
24
24
  expect(formatToPlainText({ a: 1 })).toBe(JSON.stringify({ a: 1 }, null, 2));
25
25
  expect(formatToPlainText(["x", "y"])).toBe(
26
- JSON.stringify(["x", "y"], null, 2),
26
+ JSON.stringify(["x", "y"], null, 2)
27
27
  );
28
28
  });
29
29
  });
@@ -5,7 +5,7 @@ import { getUserPageLink } from "./get-user-page-link";
5
5
  describe("getSocialUrl", () => {
6
6
  test("returns our platform URL when username is provided", () => {
7
7
  expect(getSocialUrl("alice", "x", "ignored")).toBe(
8
- getUserPageLink("alice"),
8
+ getUserPageLink("alice")
9
9
  );
10
10
  });
11
11
 
@@ -16,13 +16,13 @@ describe("getSocialUrl", () => {
16
16
 
17
17
  test("builds social URL with sanitized handle", () => {
18
18
  expect(getSocialUrl(undefined, "instagram", " @john doe ")).toBe(
19
- "https://instagram.com/johndoe",
19
+ "https://instagram.com/johndoe"
20
20
  );
21
21
  });
22
22
 
23
23
  test("uses platform-specific format for youtube", () => {
24
24
  expect(getSocialUrl(undefined, "youtube", "Jane")).toBe(
25
- "https://youtube.com/@Jane",
25
+ "https://youtube.com/@Jane"
26
26
  );
27
27
  });
28
28
 
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { getSocialLink } from "./get-social-link";
3
2
  import { SupporterPlatforms } from "../constants";
3
+ import { getSocialLink } from "./get-social-link";
4
4
 
5
5
  describe("getSocialLink", () => {
6
6
  test("returns null when username or platform is missing", () => {
@@ -11,43 +11,43 @@ describe("getSocialLink", () => {
11
11
 
12
12
  test("returns correct Facebook URL", () => {
13
13
  expect(getSocialLink("alice", SupporterPlatforms.FACEBOOK)).toBe(
14
- "https://facebook.com/alice",
14
+ "https://facebook.com/alice"
15
15
  );
16
16
  });
17
17
 
18
18
  test("returns correct Instagram URL with spaces", () => {
19
19
  expect(getSocialLink(" john doe ", SupporterPlatforms.INSTAGRAM)).toBe(
20
- "https://instagram.com/johndoe",
20
+ "https://instagram.com/johndoe"
21
21
  );
22
22
  });
23
23
 
24
24
  test("returns correct TikTok URL", () => {
25
25
  expect(getSocialLink("user123", SupporterPlatforms.TIKTOK)).toBe(
26
- "https://tiktok.com/@user123",
26
+ "https://tiktok.com/@user123"
27
27
  );
28
28
  });
29
29
 
30
30
  test("returns correct YouTube URL", () => {
31
31
  expect(getSocialLink("Jane", SupporterPlatforms.YOUTUBE)).toBe(
32
- "https://youtube.com/Jane",
32
+ "https://youtube.com/Jane"
33
33
  );
34
34
  });
35
35
 
36
36
  test("returns correct X (Twitter) URL", () => {
37
37
  expect(getSocialLink("bob", SupporterPlatforms.X)).toBe(
38
- "https://x.com/bob",
38
+ "https://x.com/bob"
39
39
  );
40
40
  });
41
41
 
42
42
  test("returns correct LinkedIn URL", () => {
43
43
  expect(getSocialLink("johnsmith", SupporterPlatforms.LINKEDIN)).toBe(
44
- "https://linkedin.com/in/johnsmith",
44
+ "https://linkedin.com/in/johnsmith"
45
45
  );
46
46
  });
47
47
 
48
48
  test("returns correct GitHub URL", () => {
49
49
  expect(getSocialLink("octocat", SupporterPlatforms.GITHUB)).toBe(
50
- "https://github.com/octocat",
50
+ "https://github.com/octocat"
51
51
  );
52
52
  });
53
53
 
@@ -57,7 +57,7 @@ describe("getSocialLink", () => {
57
57
 
58
58
  test("sanitizes username with special characters", () => {
59
59
  expect(getSocialLink("user name!@#", SupporterPlatforms.FACEBOOK)).toBe(
60
- "https://facebook.com/username!%40%23",
60
+ "https://facebook.com/username!%40%23"
61
61
  );
62
62
  });
63
63
  });
@@ -1,10 +1,12 @@
1
- import { SupporterPlatforms, type SupporterPlatform } from "../constants";
1
+ import { type SupporterPlatform, SupporterPlatforms } from "../constants";
2
2
 
3
3
  export function getSocialLink(username?: string, platform?: SupporterPlatform) {
4
- if (!username || !platform) return null;
4
+ if (!(username && platform)) {
5
+ return null;
6
+ }
5
7
 
6
8
  const sanitizedUsername = encodeURIComponent(
7
- username.trim().replace(/\s+/g, ""),
9
+ username.trim().replace(/\s+/g, "")
8
10
  );
9
11
 
10
12
  switch (platform) {
@@ -1,5 +1,5 @@
1
- import { getInitials } from "./get-user-name-initials";
2
1
  import { describe, expect, test } from "bun:test";
2
+ import { getInitials } from "./get-user-name-initials";
3
3
 
4
4
  describe("getInitials", () => {
5
5
  test("returns initials for a full name", () => {
@@ -1,8 +1,8 @@
1
1
  const regexPattern = /\s+/;
2
2
 
3
3
  export function getInitials(name?: string | null) {
4
- const parts = name?.trim().split(regexPattern).filter(Boolean) ?? [];
5
- const first = parts[0]?.[0] ?? "?";
6
- const last = parts.length > 1 ? (parts.at(-1)?.[0] ?? "") : "";
7
- return `${first}${last}`.toUpperCase();
4
+ const parts = name?.trim().split(regexPattern).filter(Boolean) ?? [];
5
+ const first = parts[0]?.[0] ?? "?";
6
+ const last = parts.length > 1 ? (parts.at(-1)?.[0] ?? "") : "";
7
+ return `${first}${last}`.toUpperCase();
8
8
  }
@@ -1,6 +1,6 @@
1
1
  export function getUserPageLink(username: string) {
2
2
  const sanitizedUsername = encodeURIComponent(
3
- username.trim().replace(/\s+/g, ""),
3
+ username.trim().replace(/\s+/g, "")
4
4
  );
5
5
  return `https://hobenakicoffee.com/@${sanitizedUsername}`;
6
6
  }
@@ -1,7 +1,11 @@
1
+ export * from "./check-moderation";
1
2
  export * from "./format-amount";
2
3
  export * from "./format-date";
4
+ export * from "./format-number";
3
5
  export * from "./format-plain-text";
4
6
  export * from "./get-social-handle";
7
+ export * from "./get-social-link";
8
+ export * from "./get-user-name-initials";
5
9
  export * from "./get-user-page-link";
6
10
  export * from "./open-to-new-window";
7
11
  export * from "./post-to-facebook";
@@ -9,9 +13,5 @@ export * from "./post-to-instagram";
9
13
  export * from "./post-to-linkedin";
10
14
  export * from "./post-to-x";
11
15
  export * from "./qr-svg-utils";
12
- export * from "./validate-phone-number";
13
16
  export * from "./to-human-readable";
14
- export * from "./get-social-link";
15
- export * from "./format-number";
16
- export * from "./get-user-name-initials";
17
- export * from "./check-moderation";
17
+ export * from "./validate-phone-number";
@@ -1,5 +1,7 @@
1
1
  export const openInNewWindow = (url: string): void => {
2
- if (typeof window === "undefined") return;
2
+ if (typeof window === "undefined") {
3
+ return;
4
+ }
3
5
 
4
6
  window.open(url, "_blank", "noopener,noreferrer");
5
7
  };
@@ -10,7 +10,7 @@ describe("shareToFacebook", () => {
10
10
 
11
11
  test("throws when url is missing", () => {
12
12
  expect(() => shareToFacebook({ url: "" })).toThrow(
13
- "Facebook share requires a URL",
13
+ "Facebook share requires a URL"
14
14
  );
15
15
  });
16
16
 
@@ -19,9 +19,15 @@ export function shareToFacebook({
19
19
  u: url,
20
20
  });
21
21
 
22
- if (quote) params.append("quote", quote);
23
- if (hashtag) params.append("hashtag", hashtag);
24
- if (ref) params.append("ref", ref);
22
+ if (quote) {
23
+ params.append("quote", quote);
24
+ }
25
+ if (hashtag) {
26
+ params.append("hashtag", hashtag);
27
+ }
28
+ if (ref) {
29
+ params.append("ref", ref);
30
+ }
25
31
 
26
32
  const shareUrl = `https://www.facebook.com/sharer/sharer.php?${params.toString()}`;
27
33
 
@@ -26,7 +26,7 @@ describe("shareToInstagram", () => {
26
26
  const { shareToInstagram } = await import("./post-to-instagram");
27
27
 
28
28
  expect(() => shareToInstagram({ url: "" })).toThrow(
29
- "Instagram share requires a URL",
29
+ "Instagram share requires a URL"
30
30
  );
31
31
  });
32
32
 
@@ -10,7 +10,7 @@ describe("shareToLinkedIn", () => {
10
10
 
11
11
  test("throws when url is missing", () => {
12
12
  expect(() => shareToLinkedIn({ url: "" })).toThrow(
13
- "LinkedIn share requires a URL",
13
+ "LinkedIn share requires a URL"
14
14
  );
15
15
  });
16
16
 
@@ -19,9 +19,15 @@ export function shareToLinkedIn({
19
19
  url,
20
20
  });
21
21
 
22
- if (title) params.append("title", title);
23
- if (summary) params.append("summary", summary);
24
- if (source) params.append("source", source);
22
+ if (title) {
23
+ params.append("title", title);
24
+ }
25
+ if (summary) {
26
+ params.append("summary", summary);
27
+ }
28
+ if (source) {
29
+ params.append("source", source);
30
+ }
25
31
 
26
32
  const shareUrl = `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`;
27
33
 
@@ -10,7 +10,7 @@ describe("shareToX", () => {
10
10
 
11
11
  test("throws when text is missing", () => {
12
12
  expect(() => shareToX({ text: "" })).toThrow(
13
- "X share requires text content",
13
+ "X share requires text content"
14
14
  );
15
15
  });
16
16
 
@@ -15,10 +15,18 @@ export function shareToX({ text, url, hashtags, via, related }: XShareOptions) {
15
15
  text,
16
16
  });
17
17
 
18
- if (url) params.append("url", url);
19
- if (hashtags) params.append("hashtags", hashtags);
20
- if (via) params.append("via", via);
21
- if (related) params.append("related", related);
18
+ if (url) {
19
+ params.append("url", url);
20
+ }
21
+ if (hashtags) {
22
+ params.append("hashtags", hashtags);
23
+ }
24
+ if (via) {
25
+ params.append("via", via);
26
+ }
27
+ if (related) {
28
+ params.append("related", related);
29
+ }
22
30
 
23
31
  const shareUrl = `https://twitter.com/intent/tweet?${params.toString()}`;
24
32
 
@@ -9,12 +9,16 @@ export function toHumanReadable(input: string): string {
9
9
  .replace(/\s+/g, " ")
10
10
  .trim();
11
11
 
12
- if (!normalized) return "";
12
+ if (!normalized) {
13
+ return "";
14
+ }
13
15
 
14
16
  return normalized
15
17
  .split(" ")
16
18
  .map((word) => {
17
- if (UPPERCASE_OR_NUMBER.test(word)) return word;
19
+ if (UPPERCASE_OR_NUMBER.test(word)) {
20
+ return word;
21
+ }
18
22
 
19
23
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
20
24
  })
@@ -25,4 +25,4 @@ describe("validatePhoneNumber", () => {
25
25
  test("returns false for non-numeric input", () => {
26
26
  expect(validatePhoneNumber("abc01712345678")).toBe(false);
27
27
  });
28
- });
28
+ });