@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.
- package/README.md +25 -4
- package/package.json +84 -9
- package/src/App.tsx +28 -0
- package/src/components/turnstile-captcha.tsx +47 -0
- package/src/components/ui/alert-dialog.tsx +196 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/avatar.tsx +110 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/breadcrumb.tsx +122 -0
- package/src/components/ui/button-group.tsx +82 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/calendar.tsx +235 -0
- package/src/components/ui/card.tsx +100 -0
- package/src/components/ui/chart.tsx +364 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/dialog.tsx +162 -0
- package/src/components/ui/drawer.tsx +126 -0
- package/src/components/ui/dropdown-menu.tsx +267 -0
- package/src/components/ui/empty-minimal.tsx +20 -0
- package/src/components/ui/empty.tsx +101 -0
- package/src/components/ui/field.tsx +235 -0
- package/src/components/ui/input-group.tsx +170 -0
- package/src/components/ui/input-otp.tsx +84 -0
- package/src/components/ui/input.tsx +37 -0
- package/src/components/ui/item.tsx +196 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/radio-group.tsx +47 -0
- package/src/components/ui/select.tsx +205 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +141 -0
- package/src/components/ui/sidebar.tsx +699 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +74 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/table.tsx +114 -0
- package/src/components/ui/tabs.tsx +88 -0
- package/src/components/ui/textarea.tsx +35 -0
- package/src/components/ui/toggle-group.tsx +91 -0
- package/src/components/ui/toggle.tsx +44 -0
- package/src/components/ui/tooltip.tsx +59 -0
- package/src/constants/common.test.ts +1 -1
- package/src/constants/legal.test.ts +1 -1
- package/src/constants/payment.test.ts +9 -9
- package/src/constants/platforms.test.ts +1 -1
- package/src/constants/services.test.ts +1 -1
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.css +135 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +16 -0
- package/src/moderation/datasets/bn.ts +708 -708
- package/src/moderation/normalizer.test.ts +1 -1
- package/src/moderation/normalizer.ts +16 -16
- package/src/moderation/profanity-service.test.ts +3 -3
- package/src/providers/theme-provider.tsx +73 -0
- package/src/types/supabase.ts +751 -647
- package/src/utils/check-moderation.test.ts +12 -12
- package/src/utils/format-number.test.ts +1 -1
- package/src/utils/format-plain-text.test.ts +1 -1
- package/src/utils/get-social-handle.test.ts +3 -3
- package/src/utils/get-social-link.test.ts +9 -9
- package/src/utils/get-social-link.ts +5 -3
- package/src/utils/get-user-name-initials.test.ts +1 -1
- package/src/utils/get-user-name-initials.ts +4 -4
- package/src/utils/get-user-page-link.ts +1 -1
- package/src/utils/index.ts +5 -5
- package/src/utils/open-to-new-window.ts +3 -1
- package/src/utils/post-to-facebook.test.ts +1 -1
- package/src/utils/post-to-facebook.ts +9 -3
- package/src/utils/post-to-instagram.test.ts +1 -1
- package/src/utils/post-to-linkedin.test.ts +1 -1
- package/src/utils/post-to-linkedin.ts +9 -3
- package/src/utils/post-to-x.test.ts +1 -1
- package/src/utils/post-to-x.ts +12 -4
- package/src/utils/to-human-readable.ts +6 -2
- package/src/utils/validate-phone-number.test.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, expect,
|
|
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 (
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
280
|
+
mockModerations.create = mock(() => {
|
|
281
281
|
throw new Error(errorMessage);
|
|
282
282
|
});
|
|
283
283
|
|
|
@@ -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 {
|
|
1
|
+
import { type SupporterPlatform, SupporterPlatforms } from "../constants";
|
|
2
2
|
|
|
3
3
|
export function getSocialLink(username?: string, platform?: SupporterPlatform) {
|
|
4
|
-
if (!username
|
|
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,8 +1,8 @@
|
|
|
1
1
|
const regexPattern = /\s+/;
|
|
2
2
|
|
|
3
3
|
export function getInitials(name?: string | null) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -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 "./
|
|
15
|
-
export * from "./format-number";
|
|
16
|
-
export * from "./get-user-name-initials";
|
|
17
|
-
export * from "./check-moderation";
|
|
17
|
+
export * from "./validate-phone-number";
|
|
@@ -19,9 +19,15 @@ export function shareToFacebook({
|
|
|
19
19
|
u: url,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
if (quote)
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
|
@@ -19,9 +19,15 @@ export function shareToLinkedIn({
|
|
|
19
19
|
url,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
if (title)
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
package/src/utils/post-to-x.ts
CHANGED
|
@@ -15,10 +15,18 @@ export function shareToX({ text, url, hashtags, via, related }: XShareOptions) {
|
|
|
15
15
|
text,
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
if (url)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (
|
|
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)
|
|
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))
|
|
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
|
})
|