@hobenakicoffee/libraries 3.4.2 → 4.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 +21 -0
- package/README.md +119 -246
- package/package.json +25 -22
- package/src/App.tsx +194 -19
- package/src/index.css +0 -1
- package/src/types/supabase.ts +940 -3
- package/src/components/turnstile-captcha.tsx +0 -47
- package/src/components/ui/button.tsx +0 -77
- package/src/components/ui/calendar.tsx +0 -235
- package/src/components/ui/spinner.tsx +0 -18
- package/src/constants/common.test.ts +0 -33
- package/src/constants/legal.test.ts +0 -72
- package/src/constants/payment.test.ts +0 -259
- package/src/constants/platforms.test.ts +0 -66
- package/src/constants/services.test.ts +0 -58
- package/src/lib/utils.ts +0 -6
- package/src/moderation/profanity-service.test.ts +0 -106
- package/src/providers/theme-provider.tsx +0 -73
- package/src/utils/check-moderation.test.ts +0 -321
- package/src/utils/format-amount.test.ts +0 -30
- package/src/utils/format-count.test.ts +0 -56
- package/src/utils/format-date.test.ts +0 -19
- package/src/utils/format-number.test.ts +0 -29
- package/src/utils/format-plain-text.test.ts +0 -36
- package/src/utils/get-newsletter-post-link.test.ts +0 -27
- package/src/utils/get-product-link.test.ts +0 -34
- package/src/utils/get-social-handle.test.ts +0 -32
- package/src/utils/get-social-link.test.ts +0 -63
- package/src/utils/get-user-name-initials.test.ts +0 -34
- package/src/utils/get-user-page-link.test.ts +0 -9
- package/src/utils/open-to-new-window.test.ts +0 -34
- package/src/utils/post-to-facebook.test.ts +0 -43
- package/src/utils/post-to-instagram.test.ts +0 -56
- package/src/utils/post-to-linkedin.test.ts +0 -43
- package/src/utils/post-to-x.test.ts +0 -45
- package/src/utils/qr-svg-utils.test.ts +0 -104
- package/src/utils/to-human-readable.test.ts +0 -25
- package/src/utils/validate-phone-number.test.ts +0 -28
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile";
|
|
2
|
-
import { useRef } from "react";
|
|
3
|
-
import { useTheme } from "@/providers/theme-provider";
|
|
4
|
-
|
|
5
|
-
interface TurnstileCaptchaProps {
|
|
6
|
-
onError?: (error: string) => void;
|
|
7
|
-
onTokenChange: (token: string) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function TurnstileCaptcha({
|
|
11
|
-
onTokenChange,
|
|
12
|
-
onError,
|
|
13
|
-
}: TurnstileCaptchaProps) {
|
|
14
|
-
const { theme } = useTheme();
|
|
15
|
-
const turnstileRef = useRef<TurnstileInstance | null>(null);
|
|
16
|
-
|
|
17
|
-
function handleSuccess(token: string) {
|
|
18
|
-
onTokenChange(token);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function handleExpire() {
|
|
22
|
-
onTokenChange("");
|
|
23
|
-
onError?.("CAPTCHA has expired. Please verify again.");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function handleError() {
|
|
27
|
-
onTokenChange("");
|
|
28
|
-
onError?.("CAPTCHA verification failed. Please try again.");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="rounded-xl border border-input bg-input">
|
|
33
|
-
<Turnstile
|
|
34
|
-
className="overflow-hidden rounded-[calc(var(--radius)+5px)]"
|
|
35
|
-
onError={handleError}
|
|
36
|
-
onExpire={handleExpire}
|
|
37
|
-
onSuccess={handleSuccess}
|
|
38
|
-
options={{
|
|
39
|
-
theme: theme === "system" ? "auto" : theme,
|
|
40
|
-
size: "flexible",
|
|
41
|
-
}}
|
|
42
|
-
ref={turnstileRef}
|
|
43
|
-
siteKey={import.meta.env.VITE_TURNSTILE_SITE_KEY || ""}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { cva, type VariantProps } from "class-variance-authority";
|
|
2
|
-
import { Slot } from "radix-ui";
|
|
3
|
-
import type * as React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
import { Spinner } from "./spinner";
|
|
7
|
-
|
|
8
|
-
const buttonVariants = cva(
|
|
9
|
-
"group/button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap rounded-xl border border-transparent bg-clip-padding font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-[0.98] disabled:pointer-events-none disabled:opacity-70 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
10
|
-
{
|
|
11
|
-
variants: {
|
|
12
|
-
variant: {
|
|
13
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
14
|
-
outline:
|
|
15
|
-
"border-border bg-input/30 hover:border-primary/50 hover:bg-input/20 hover:bg-primary/10 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
16
|
-
secondary:
|
|
17
|
-
"bg-secondary text-secondary-foreground hover:bg-primary/5 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
18
|
-
ghost:
|
|
19
|
-
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
20
|
-
destructive:
|
|
21
|
-
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 dark:hover:bg-destructive/30",
|
|
22
|
-
link: "underline-offset-4 hover:underline",
|
|
23
|
-
inverted:
|
|
24
|
-
"border-border bg-background text-foreground hover:border-primary/50 hover:bg-primary/5",
|
|
25
|
-
dottedUnderline:
|
|
26
|
-
"rounded-none border-0 border-muted-foreground/50 border-b border-dotted bg-transparent text-muted-foreground hover:bg-transparent hover:text-foreground",
|
|
27
|
-
},
|
|
28
|
-
size: {
|
|
29
|
-
default:
|
|
30
|
-
"h-12 gap-1.5 px-4 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
|
|
31
|
-
xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
|
|
32
|
-
sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
33
|
-
lg: "h-16 gap-1.5 px-5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
34
|
-
icon: "size-9",
|
|
35
|
-
"icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
36
|
-
"icon-sm": "size-8",
|
|
37
|
-
"icon-lg": "size-12",
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
defaultVariants: {
|
|
41
|
-
variant: "default",
|
|
42
|
-
size: "default",
|
|
43
|
-
},
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
function Button({
|
|
48
|
-
className,
|
|
49
|
-
variant = "default",
|
|
50
|
-
size = "default",
|
|
51
|
-
asChild = false,
|
|
52
|
-
loading,
|
|
53
|
-
disabled,
|
|
54
|
-
children,
|
|
55
|
-
...props
|
|
56
|
-
}: React.ComponentProps<"button"> &
|
|
57
|
-
VariantProps<typeof buttonVariants> & {
|
|
58
|
-
asChild?: boolean;
|
|
59
|
-
loading?: boolean;
|
|
60
|
-
}) {
|
|
61
|
-
const Comp = asChild ? Slot.Root : "button";
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<Comp
|
|
65
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
66
|
-
data-size={size}
|
|
67
|
-
data-slot="button"
|
|
68
|
-
data-variant={variant}
|
|
69
|
-
disabled={loading || disabled}
|
|
70
|
-
{...props}
|
|
71
|
-
>
|
|
72
|
-
{loading ? <Spinner /> : children}
|
|
73
|
-
</Comp>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export { Button, buttonVariants };
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ArrowDownIcon,
|
|
3
|
-
ArrowLeftIcon,
|
|
4
|
-
ArrowRightIcon,
|
|
5
|
-
} from "@hugeicons/core-free-icons";
|
|
6
|
-
import { HugeiconsIcon } from "@hugeicons/react";
|
|
7
|
-
import { type ComponentProps, useEffect, useRef } from "react";
|
|
8
|
-
import {
|
|
9
|
-
type DayButton,
|
|
10
|
-
DayPicker,
|
|
11
|
-
getDefaultClassNames,
|
|
12
|
-
} from "react-day-picker";
|
|
13
|
-
import { Button, buttonVariants } from "@/components/ui/button";
|
|
14
|
-
import { cn } from "@/lib/utils";
|
|
15
|
-
|
|
16
|
-
function Calendar({
|
|
17
|
-
className,
|
|
18
|
-
classNames,
|
|
19
|
-
showOutsideDays = true,
|
|
20
|
-
captionLayout = "label",
|
|
21
|
-
buttonVariant = "ghost",
|
|
22
|
-
formatters,
|
|
23
|
-
components,
|
|
24
|
-
...props
|
|
25
|
-
}: ComponentProps<typeof DayPicker> & {
|
|
26
|
-
buttonVariant?: ComponentProps<typeof Button>["variant"];
|
|
27
|
-
}) {
|
|
28
|
-
const defaultClassNames = getDefaultClassNames();
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<DayPicker
|
|
32
|
-
captionLayout={captionLayout}
|
|
33
|
-
className={cn(
|
|
34
|
-
"group/calendar bg-background in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent p-3 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(8)]",
|
|
35
|
-
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
|
36
|
-
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
|
37
|
-
className
|
|
38
|
-
)}
|
|
39
|
-
classNames={{
|
|
40
|
-
root: cn("w-fit", defaultClassNames.root),
|
|
41
|
-
months: cn(
|
|
42
|
-
"relative flex flex-col gap-4 md:flex-row",
|
|
43
|
-
defaultClassNames.months
|
|
44
|
-
),
|
|
45
|
-
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
|
46
|
-
nav: cn(
|
|
47
|
-
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
|
48
|
-
defaultClassNames.nav
|
|
49
|
-
),
|
|
50
|
-
button_previous: cn(
|
|
51
|
-
buttonVariants({ variant: buttonVariant }),
|
|
52
|
-
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
|
|
53
|
-
defaultClassNames.button_previous
|
|
54
|
-
),
|
|
55
|
-
button_next: cn(
|
|
56
|
-
buttonVariants({ variant: buttonVariant }),
|
|
57
|
-
"size-(--cell-size) select-none p-0 aria-disabled:opacity-50",
|
|
58
|
-
defaultClassNames.button_next
|
|
59
|
-
),
|
|
60
|
-
month_caption: cn(
|
|
61
|
-
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
|
|
62
|
-
defaultClassNames.month_caption
|
|
63
|
-
),
|
|
64
|
-
dropdowns: cn(
|
|
65
|
-
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 font-medium text-sm",
|
|
66
|
-
defaultClassNames.dropdowns
|
|
67
|
-
),
|
|
68
|
-
dropdown_root: cn(
|
|
69
|
-
"cn-calendar-dropdown-root relative rounded-(--cell-radius)",
|
|
70
|
-
defaultClassNames.dropdown_root
|
|
71
|
-
),
|
|
72
|
-
dropdown: cn(
|
|
73
|
-
"absolute inset-0 bg-popover opacity-0",
|
|
74
|
-
defaultClassNames.dropdown
|
|
75
|
-
),
|
|
76
|
-
caption_label: cn(
|
|
77
|
-
"select-none font-medium",
|
|
78
|
-
captionLayout === "label"
|
|
79
|
-
? "text-sm"
|
|
80
|
-
: "cn-calendar-caption-label flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
|
81
|
-
defaultClassNames.caption_label
|
|
82
|
-
),
|
|
83
|
-
table: "w-full border-collapse",
|
|
84
|
-
weekdays: cn("flex", defaultClassNames.weekdays),
|
|
85
|
-
weekday: cn(
|
|
86
|
-
"flex-1 select-none rounded-(--cell-radius) font-normal text-[0.8rem] text-muted-foreground",
|
|
87
|
-
defaultClassNames.weekday
|
|
88
|
-
),
|
|
89
|
-
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
|
90
|
-
week_number_header: cn(
|
|
91
|
-
"w-(--cell-size) select-none",
|
|
92
|
-
defaultClassNames.week_number_header
|
|
93
|
-
),
|
|
94
|
-
week_number: cn(
|
|
95
|
-
"select-none text-[0.8rem] text-muted-foreground",
|
|
96
|
-
defaultClassNames.week_number
|
|
97
|
-
),
|
|
98
|
-
day: cn(
|
|
99
|
-
"group/day relative aspect-square h-full w-full select-none rounded-(--cell-radius) p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
|
|
100
|
-
props.showWeekNumber
|
|
101
|
-
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
|
|
102
|
-
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
|
|
103
|
-
defaultClassNames.day
|
|
104
|
-
),
|
|
105
|
-
range_start: cn(
|
|
106
|
-
"relative isolate -z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
|
|
107
|
-
defaultClassNames.range_start
|
|
108
|
-
),
|
|
109
|
-
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
|
110
|
-
range_end: cn(
|
|
111
|
-
"relative isolate -z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted-200",
|
|
112
|
-
defaultClassNames.range_end
|
|
113
|
-
),
|
|
114
|
-
today: cn(
|
|
115
|
-
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
|
|
116
|
-
defaultClassNames.today
|
|
117
|
-
),
|
|
118
|
-
outside: cn(
|
|
119
|
-
"text-muted-foreground aria-selected:text-muted-foreground",
|
|
120
|
-
defaultClassNames.outside
|
|
121
|
-
),
|
|
122
|
-
disabled: cn(
|
|
123
|
-
"text-muted-foreground opacity-50",
|
|
124
|
-
defaultClassNames.disabled
|
|
125
|
-
),
|
|
126
|
-
hidden: cn("invisible", defaultClassNames.hidden),
|
|
127
|
-
...classNames,
|
|
128
|
-
}}
|
|
129
|
-
components={{
|
|
130
|
-
Root: ({ className, rootRef, ...props }) => {
|
|
131
|
-
return (
|
|
132
|
-
<div
|
|
133
|
-
className={cn(className)}
|
|
134
|
-
data-slot="calendar"
|
|
135
|
-
ref={rootRef}
|
|
136
|
-
{...props}
|
|
137
|
-
/>
|
|
138
|
-
);
|
|
139
|
-
},
|
|
140
|
-
Chevron: ({ className, orientation, ...props }) => {
|
|
141
|
-
if (orientation === "left") {
|
|
142
|
-
return (
|
|
143
|
-
<HugeiconsIcon
|
|
144
|
-
className={cn("size-4", className)}
|
|
145
|
-
icon={ArrowLeftIcon}
|
|
146
|
-
strokeWidth={2}
|
|
147
|
-
{...props}
|
|
148
|
-
/>
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (orientation === "right") {
|
|
153
|
-
return (
|
|
154
|
-
<HugeiconsIcon
|
|
155
|
-
className={cn("size-4", className)}
|
|
156
|
-
icon={ArrowRightIcon}
|
|
157
|
-
strokeWidth={2}
|
|
158
|
-
{...props}
|
|
159
|
-
/>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<HugeiconsIcon
|
|
165
|
-
className={cn("size-4", className)}
|
|
166
|
-
icon={ArrowDownIcon}
|
|
167
|
-
strokeWidth={2}
|
|
168
|
-
{...props}
|
|
169
|
-
/>
|
|
170
|
-
);
|
|
171
|
-
},
|
|
172
|
-
DayButton: CalendarDayButton,
|
|
173
|
-
WeekNumber: ({ children, ...props }) => {
|
|
174
|
-
return (
|
|
175
|
-
<td {...props}>
|
|
176
|
-
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
|
177
|
-
{children}
|
|
178
|
-
</div>
|
|
179
|
-
</td>
|
|
180
|
-
);
|
|
181
|
-
},
|
|
182
|
-
...components,
|
|
183
|
-
}}
|
|
184
|
-
formatters={{
|
|
185
|
-
formatMonthDropdown: (date) =>
|
|
186
|
-
date.toLocaleString("default", { month: "short" }),
|
|
187
|
-
...formatters,
|
|
188
|
-
}}
|
|
189
|
-
showOutsideDays={showOutsideDays}
|
|
190
|
-
{...props}
|
|
191
|
-
/>
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function CalendarDayButton({
|
|
196
|
-
className,
|
|
197
|
-
day,
|
|
198
|
-
modifiers,
|
|
199
|
-
...props
|
|
200
|
-
}: ComponentProps<typeof DayButton>) {
|
|
201
|
-
const defaultClassNames = getDefaultClassNames();
|
|
202
|
-
|
|
203
|
-
const ref = useRef<HTMLButtonElement>(null);
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
if (modifiers.focused) {
|
|
206
|
-
ref.current?.focus();
|
|
207
|
-
}
|
|
208
|
-
}, [modifiers.focused]);
|
|
209
|
-
|
|
210
|
-
return (
|
|
211
|
-
<Button
|
|
212
|
-
className={cn(
|
|
213
|
-
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 font-normal leading-none data-[range-end=true]:rounded-(--cell-radius) data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-end=true]:bg-primary data-[range-middle=true]:bg-muted data-[range-start=true]:bg-primary data-[selected-single=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:text-foreground data-[range-start=true]:text-primary-foreground data-[selected-single=true]:text-primary-foreground group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
|
214
|
-
defaultClassNames.day,
|
|
215
|
-
className
|
|
216
|
-
)}
|
|
217
|
-
data-day={day.date.toLocaleDateString()}
|
|
218
|
-
data-range-end={modifiers.range_end}
|
|
219
|
-
data-range-middle={modifiers.range_middle}
|
|
220
|
-
data-range-start={modifiers.range_start}
|
|
221
|
-
data-selected-single={
|
|
222
|
-
modifiers.selected &&
|
|
223
|
-
!modifiers.range_start &&
|
|
224
|
-
!modifiers.range_end &&
|
|
225
|
-
!modifiers.range_middle
|
|
226
|
-
}
|
|
227
|
-
ref={ref}
|
|
228
|
-
size="icon"
|
|
229
|
-
variant="ghost"
|
|
230
|
-
{...props}
|
|
231
|
-
/>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export { Calendar, CalendarDayButton };
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Loading03Icon } from "@hugeicons/core-free-icons";
|
|
2
|
-
import { HugeiconsIcon } from "@hugeicons/react";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
|
|
5
|
-
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
6
|
-
return (
|
|
7
|
-
<HugeiconsIcon
|
|
8
|
-
aria-label="Loading"
|
|
9
|
-
className={cn("size-4 animate-spin", className)}
|
|
10
|
-
icon={Loading03Icon}
|
|
11
|
-
role="status"
|
|
12
|
-
{...props}
|
|
13
|
-
strokeWidth={2}
|
|
14
|
-
/>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export { Spinner };
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { Visibility as VisibilityType } from "./common";
|
|
3
|
-
import { Visibility } from "./common";
|
|
4
|
-
|
|
5
|
-
describe("Visibility", () => {
|
|
6
|
-
test("should contain all expected keys", () => {
|
|
7
|
-
const expectedKeys = ["PUBLIC", "PRIVATE"];
|
|
8
|
-
expect(Object.keys(Visibility)).toEqual(expectedKeys);
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
test("should have correct values for each visibility", () => {
|
|
12
|
-
expect(Visibility.PUBLIC).toBe("public");
|
|
13
|
-
expect(Visibility.PRIVATE).toBe("private");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("should be usable as Visibility type", () => {
|
|
17
|
-
const pub: VisibilityType = "public";
|
|
18
|
-
const priv: VisibilityType = "private";
|
|
19
|
-
expect(pub).toBe("public");
|
|
20
|
-
expect(priv).toBe("private");
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("should have 2 visibilities", () => {
|
|
24
|
-
expect(Object.keys(Visibility).length).toBe(2);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("all values should be lowercase strings", () => {
|
|
28
|
-
Object.values(Visibility).forEach((v) => {
|
|
29
|
-
expect(v).toBe(v.toLowerCase() as VisibilityType);
|
|
30
|
-
expect(typeof v).toBe("string");
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
});
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { companyInfo, productInfo } from "./legal";
|
|
3
|
-
|
|
4
|
-
describe("productInfo", () => {
|
|
5
|
-
test("should have all required fields", () => {
|
|
6
|
-
expect(productInfo).toHaveProperty("name");
|
|
7
|
-
expect(productInfo).toHaveProperty("domain");
|
|
8
|
-
expect(productInfo).toHaveProperty("twitterHandle");
|
|
9
|
-
expect(productInfo).toHaveProperty("title");
|
|
10
|
-
expect(productInfo).toHaveProperty("description");
|
|
11
|
-
expect(productInfo).toHaveProperty("keywords");
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("should match exact values", () => {
|
|
15
|
-
expect(productInfo).toEqual({
|
|
16
|
-
name: "হবে নাকি Coffee?",
|
|
17
|
-
domain: "https://www.hobenakicoffee.com",
|
|
18
|
-
twitterHandle: "@hobenakicoffee",
|
|
19
|
-
title: "চিন্তা ছাড়া আয়, শুরু করো তাই — হবে নাকি Coffee, ঝামেলা সব ছাই!",
|
|
20
|
-
description:
|
|
21
|
-
"‘হবে নাকি Coffee?’ দিয়ে যে কেউ ঝামেলা ছাড়াই নিজের অনলাইন ব্যবসা বা আয় শুরু করতে পারে। লিগ্যাল, কোম্পানি ফরমেশন বা কাগজপত্র নিয়ে ভাবতে হবে না—সবকিছু আমরা দেখি। শুধু একটি লিংক শেয়ার করুন, আর যে কেউ Coffee/Tip দিয়ে আপনাকে সাপোর্ট করতে পারবে। চাইলে এক্সক্লুসিভ কনটেন্ট, ডিজিটাল পণ্য বা মার্চ, গিফট, পার্সোনাল শাউটআউট কিংবা সার্ভিসও অফার করা যায়। কনটেন্ট ক্রিয়েটর, ফ্রিল্যান্সার, ছোট ব্যবসা, কমিউনিটি বা ইভেন্ট অর্গানাইজার, চ্যারিটি উদ্যোগ বা ব্যক্তিগত লক্ষ্য—সবার জন্যই HobeNakiCoffee হলো অনলাইনে আয় ও সাপোর্ট নেওয়ার সহজ, ঝামেলামুক্ত প্ল্যাটফর্ম।",
|
|
22
|
-
keywords:
|
|
23
|
-
"হবে নাকি Coffee, buy me a coffee, tip jar, support link, support me, donate, online income bangla, extra income, monetization, payment link, audience support, exclusive content, digital product, merch, shoutout, fundraising, charity, small business support, freelancer support, Bangladesh",
|
|
24
|
-
socials: {
|
|
25
|
-
facebook: "https://www.facebook.com/hobenakicoffee",
|
|
26
|
-
youtube: "https://www.youtube.com/@hobenakicoffee",
|
|
27
|
-
x: "https://x.com/hobenakicoffee",
|
|
28
|
-
instagram: "https://www.instagram.com/hobenakicoffee",
|
|
29
|
-
tiktok: "https://www.tiktok.com/@hobenakicoffee",
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("companyInfo", () => {
|
|
36
|
-
test("should have all required fields", () => {
|
|
37
|
-
expect(companyInfo).toHaveProperty("name");
|
|
38
|
-
expect(companyInfo).toHaveProperty("contactEmail");
|
|
39
|
-
expect(companyInfo).toHaveProperty("contactPhone");
|
|
40
|
-
expect(companyInfo).toHaveProperty("contactLocation");
|
|
41
|
-
expect(companyInfo).toHaveProperty("contactWhatsapp");
|
|
42
|
-
expect(companyInfo).toHaveProperty("contactX");
|
|
43
|
-
expect(companyInfo).toHaveProperty("contactFacebook");
|
|
44
|
-
expect(companyInfo).toHaveProperty("contactYoutube");
|
|
45
|
-
expect(companyInfo).toHaveProperty("contactLinkedin");
|
|
46
|
-
expect(companyInfo).toHaveProperty("domain");
|
|
47
|
-
expect(companyInfo).toHaveProperty("postalAddress");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("should match exact values", () => {
|
|
51
|
-
expect(companyInfo).toEqual({
|
|
52
|
-
name: "Shamscorner LLC",
|
|
53
|
-
contactEmail: "mail@shamscorner.com",
|
|
54
|
-
contactPhone: "+1(817) 973-7285",
|
|
55
|
-
contactLocation:
|
|
56
|
-
"7320, 1021 E Lincolnway, Cheyenne, WY, Laramie, US, 82001",
|
|
57
|
-
contactWhatsapp: "https://wa.me/13072212615",
|
|
58
|
-
contactX: "https://www.x.com/shamscorner_llc",
|
|
59
|
-
contactFacebook: "https://www.facebook.com/shamscorner.llc",
|
|
60
|
-
contactYoutube: "https://www.youtube.com/@shamscorner",
|
|
61
|
-
contactLinkedin: "https://www.linkedin.com/company/shamscorner",
|
|
62
|
-
domain: "https://www.shamscorner.com",
|
|
63
|
-
postalAddress: {
|
|
64
|
-
streetAddress: "1021 E Lincolnway Suite 7320",
|
|
65
|
-
addressLocality: "Cheyenne",
|
|
66
|
-
addressRegion: "WY",
|
|
67
|
-
postalCode: "82001",
|
|
68
|
-
addressCountry: "US",
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
});
|