hazo_notify 1.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.
- package/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +16 -0
- package/.cursor/rules/general.mdc +49 -0
- package/README.md +765 -0
- package/components/emailer-html-editor.tsx +94 -0
- package/components/ui/button.tsx +53 -0
- package/components/ui/card.tsx +78 -0
- package/components/ui/input.tsx +24 -0
- package/components/ui/label.tsx +21 -0
- package/components/ui/sidebar.tsx +121 -0
- package/components/ui/spinner.tsx +54 -0
- package/components/ui/textarea.tsx +23 -0
- package/components/ui/tooltip.tsx +30 -0
- package/components.json +20 -0
- package/hazo_notify_config.ini +153 -0
- package/jest.config.js +27 -0
- package/jest.setup.js +1 -0
- package/next.config.js +22 -0
- package/package.json +72 -0
- package/postcss.config.js +6 -0
- package/src/app/api/hazo_notify/emailer/send/__tests__/route.test.ts +227 -0
- package/src/app/api/hazo_notify/emailer/send/route.ts +537 -0
- package/src/app/editor-00/page.tsx +47 -0
- package/src/app/globals.css +69 -0
- package/src/app/hazo_notify/emailer_test/layout.tsx +53 -0
- package/src/app/hazo_notify/emailer_test/page.tsx +369 -0
- package/src/app/hazo_notify/layout.tsx +77 -0
- package/src/app/hazo_notify/page.tsx +12 -0
- package/src/app/layout.tsx +26 -0
- package/src/app/page.tsx +14 -0
- package/src/components/blocks/editor-00/editor.tsx +61 -0
- package/src/components/blocks/editor-00/nodes.ts +11 -0
- package/src/components/blocks/editor-00/plugins.tsx +36 -0
- package/src/components/editor/editor-ui/content-editable.tsx +34 -0
- package/src/components/editor/themes/editor-theme.css +91 -0
- package/src/components/editor/themes/editor-theme.ts +130 -0
- package/src/components/ui/button.tsx +53 -0
- package/src/components/ui/card.tsx +78 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/sidebar.tsx +121 -0
- package/src/components/ui/spinner.tsx +54 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/lib/emailer/__tests__/emailer.test.ts +200 -0
- package/src/lib/emailer/emailer.ts +263 -0
- package/src/lib/emailer/index.ts +11 -0
- package/src/lib/emailer/providers/__tests__/zeptomail_provider.test.ts +196 -0
- package/src/lib/emailer/providers/index.ts +33 -0
- package/src/lib/emailer/providers/pop3_provider.ts +30 -0
- package/src/lib/emailer/providers/smtp_provider.ts +30 -0
- package/src/lib/emailer/providers/zeptomail_provider.ts +299 -0
- package/src/lib/emailer/types.ts +119 -0
- package/src/lib/emailer/utils/constants.ts +24 -0
- package/src/lib/emailer/utils/index.ts +9 -0
- package/src/lib/emailer/utils/logger.ts +71 -0
- package/src/lib/emailer/utils/validation.ts +84 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/utils.ts +6 -0
- package/tailwind.config.ts +65 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom HTML editor component for emailer test page
|
|
3
|
+
* Wraps the Lexical editor with HTML extraction functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import { SerializedEditorState, EditorState } from 'lexical';
|
|
9
|
+
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
|
10
|
+
import { $generateHtmlFromNodes } from '@lexical/html';
|
|
11
|
+
import { useEffect } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
InitialConfigType,
|
|
14
|
+
LexicalComposer,
|
|
15
|
+
} from '@lexical/react/LexicalComposer';
|
|
16
|
+
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
|
17
|
+
import { editorTheme } from '@/components/editor/themes/editor-theme';
|
|
18
|
+
import { TooltipProvider } from '@/components/ui/tooltip';
|
|
19
|
+
import { nodes } from '@/components/blocks/editor-00/nodes';
|
|
20
|
+
import { Plugins } from '@/components/blocks/editor-00/plugins';
|
|
21
|
+
|
|
22
|
+
const editorConfig: InitialConfigType = {
|
|
23
|
+
namespace: 'EmailerEditor',
|
|
24
|
+
theme: editorTheme,
|
|
25
|
+
nodes,
|
|
26
|
+
onError: (error: Error) => {
|
|
27
|
+
console.error(error);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Plugin component to extract HTML from editor
|
|
33
|
+
* Must be used inside LexicalComposer context
|
|
34
|
+
*/
|
|
35
|
+
function EditorHtmlExtractorPlugin({
|
|
36
|
+
onHtmlChange
|
|
37
|
+
}: {
|
|
38
|
+
onHtmlChange: (html: string) => void
|
|
39
|
+
}) {
|
|
40
|
+
const [editor] = useLexicalComposerContext();
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
// Update HTML whenever editor state changes
|
|
44
|
+
const unregister = editor.registerUpdateListener(({ editorState }) => {
|
|
45
|
+
editorState.read(() => {
|
|
46
|
+
const html_string = $generateHtmlFromNodes(editor, null);
|
|
47
|
+
onHtmlChange(html_string);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
unregister();
|
|
53
|
+
};
|
|
54
|
+
}, [editor, onHtmlChange]);
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Custom editor component that includes HTML extraction
|
|
61
|
+
*/
|
|
62
|
+
export function EmailerHtmlEditor({
|
|
63
|
+
editorSerializedState,
|
|
64
|
+
onSerializedChange,
|
|
65
|
+
onHtmlChange
|
|
66
|
+
}: {
|
|
67
|
+
editorSerializedState?: SerializedEditorState;
|
|
68
|
+
onSerializedChange?: (value: SerializedEditorState) => void;
|
|
69
|
+
onHtmlChange: (html: string) => void;
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<div className="cls_emailer_html_editor bg-background overflow-hidden rounded-lg border shadow">
|
|
73
|
+
<LexicalComposer
|
|
74
|
+
initialConfig={{
|
|
75
|
+
...editorConfig,
|
|
76
|
+
...(editorSerializedState
|
|
77
|
+
? { editorState: JSON.stringify(editorSerializedState) }
|
|
78
|
+
: {}),
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<TooltipProvider>
|
|
82
|
+
<Plugins />
|
|
83
|
+
<EditorHtmlExtractorPlugin onHtmlChange={onHtmlChange} />
|
|
84
|
+
<OnChangePlugin
|
|
85
|
+
ignoreSelectionChange={true}
|
|
86
|
+
onChange={(editorState: EditorState) => {
|
|
87
|
+
onSerializedChange?.(editorState.toJSON());
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
</TooltipProvider>
|
|
91
|
+
</LexicalComposer>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const button_variants = cva(
|
|
6
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
11
|
+
destructive:
|
|
12
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
13
|
+
outline:
|
|
14
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
secondary:
|
|
16
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
17
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
18
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
default: "h-10 px-4 py-2",
|
|
22
|
+
sm: "h-9 rounded-md px-3",
|
|
23
|
+
lg: "h-11 rounded-md px-8",
|
|
24
|
+
icon: "h-10 w-10",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: "default",
|
|
29
|
+
size: "default",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
export interface ButtonProps
|
|
35
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
|
+
VariantProps<typeof button_variants> {
|
|
37
|
+
asChild?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
41
|
+
({ className, variant, size, ...props }, ref) => {
|
|
42
|
+
return (
|
|
43
|
+
<button
|
|
44
|
+
className={cn(button_variants({ variant, size, className }))}
|
|
45
|
+
ref={ref}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
Button.displayName = "Button"
|
|
52
|
+
|
|
53
|
+
export { Button, button_variants }
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
const Card = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn(
|
|
11
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
))
|
|
17
|
+
Card.displayName = "Card"
|
|
18
|
+
|
|
19
|
+
const CardHeader = React.forwardRef<
|
|
20
|
+
HTMLDivElement,
|
|
21
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
22
|
+
>(({ className, ...props }, ref) => (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
))
|
|
29
|
+
CardHeader.displayName = "CardHeader"
|
|
30
|
+
|
|
31
|
+
const CardTitle = React.forwardRef<
|
|
32
|
+
HTMLParagraphElement,
|
|
33
|
+
React.HTMLAttributes<HTMLHeadingElement>
|
|
34
|
+
>(({ className, ...props }, ref) => (
|
|
35
|
+
<h3
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
))
|
|
44
|
+
CardTitle.displayName = "CardTitle"
|
|
45
|
+
|
|
46
|
+
const CardDescription = React.forwardRef<
|
|
47
|
+
HTMLParagraphElement,
|
|
48
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
49
|
+
>(({ className, ...props }, ref) => (
|
|
50
|
+
<p
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
))
|
|
56
|
+
CardDescription.displayName = "CardDescription"
|
|
57
|
+
|
|
58
|
+
const CardContent = React.forwardRef<
|
|
59
|
+
HTMLDivElement,
|
|
60
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
61
|
+
>(({ className, ...props }, ref) => (
|
|
62
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
63
|
+
))
|
|
64
|
+
CardContent.displayName = "CardContent"
|
|
65
|
+
|
|
66
|
+
const CardFooter = React.forwardRef<
|
|
67
|
+
HTMLDivElement,
|
|
68
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<div
|
|
71
|
+
ref={ref}
|
|
72
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
73
|
+
{...props}
|
|
74
|
+
/>
|
|
75
|
+
))
|
|
76
|
+
CardFooter.displayName = "CardFooter"
|
|
77
|
+
|
|
78
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface InputProps
|
|
5
|
+
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
6
|
+
|
|
7
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
8
|
+
({ className, type, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<input
|
|
11
|
+
type={type}
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
ref={ref}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
Input.displayName = "Input"
|
|
23
|
+
|
|
24
|
+
export { Input }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface LabelProps
|
|
5
|
+
extends React.LabelHTMLAttributes<HTMLLabelElement> {}
|
|
6
|
+
|
|
7
|
+
const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
8
|
+
({ className, ...props }, ref) => (
|
|
9
|
+
<label
|
|
10
|
+
ref={ref}
|
|
11
|
+
className={cn(
|
|
12
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
Label.displayName = "Label"
|
|
20
|
+
|
|
21
|
+
export { Label }
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
children: React.ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
|
|
9
|
+
({ className, children, ...props }, ref) => (
|
|
10
|
+
<div
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex h-screen w-64 flex-col border-r bg-card text-card-foreground",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
Sidebar.displayName = "Sidebar"
|
|
23
|
+
|
|
24
|
+
const SidebarHeader = React.forwardRef<
|
|
25
|
+
HTMLDivElement,
|
|
26
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
27
|
+
>(({ className, ...props }, ref) => (
|
|
28
|
+
<div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn("flex h-16 items-center border-b px-6", className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
))
|
|
34
|
+
SidebarHeader.displayName = "SidebarHeader"
|
|
35
|
+
|
|
36
|
+
const SidebarContent = React.forwardRef<
|
|
37
|
+
HTMLDivElement,
|
|
38
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
39
|
+
>(({ className, ...props }, ref) => (
|
|
40
|
+
<div
|
|
41
|
+
ref={ref}
|
|
42
|
+
className={cn("flex-1 overflow-y-auto px-4 py-4", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
))
|
|
46
|
+
SidebarContent.displayName = "SidebarContent"
|
|
47
|
+
|
|
48
|
+
const SidebarFooter = React.forwardRef<
|
|
49
|
+
HTMLDivElement,
|
|
50
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
51
|
+
>(({ className, ...props }, ref) => (
|
|
52
|
+
<div
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn("border-t px-4 py-4", className)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
))
|
|
58
|
+
SidebarFooter.displayName = "SidebarFooter"
|
|
59
|
+
|
|
60
|
+
const SidebarNav = React.forwardRef<
|
|
61
|
+
HTMLNavElement,
|
|
62
|
+
React.HTMLAttributes<HTMLNavElement>
|
|
63
|
+
>(({ className, ...props }, ref) => (
|
|
64
|
+
<nav
|
|
65
|
+
ref={ref}
|
|
66
|
+
className={cn("space-y-1", className)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
))
|
|
70
|
+
SidebarNav.displayName = "SidebarNav"
|
|
71
|
+
|
|
72
|
+
const SidebarNavItem = React.forwardRef<
|
|
73
|
+
HTMLLIElement,
|
|
74
|
+
React.HTMLAttributes<HTMLLIElement>
|
|
75
|
+
>(({ className, ...props }, ref) => (
|
|
76
|
+
<li
|
|
77
|
+
ref={ref}
|
|
78
|
+
className={cn("", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
))
|
|
82
|
+
SidebarNavItem.displayName = "SidebarNavItem"
|
|
83
|
+
|
|
84
|
+
interface SidebarNavLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
85
|
+
active?: boolean
|
|
86
|
+
asChild?: boolean
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const SidebarNavLink = React.forwardRef<HTMLAnchorElement, SidebarNavLinkProps>(
|
|
90
|
+
({ className, active, asChild, children, ...props }, ref) => {
|
|
91
|
+
if (asChild) {
|
|
92
|
+
return <>{children}</>;
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
<a
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
|
99
|
+
active
|
|
100
|
+
? "bg-accent text-accent-foreground"
|
|
101
|
+
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
102
|
+
className
|
|
103
|
+
)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</a>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
SidebarNavLink.displayName = "SidebarNavLink"
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
Sidebar,
|
|
115
|
+
SidebarHeader,
|
|
116
|
+
SidebarContent,
|
|
117
|
+
SidebarFooter,
|
|
118
|
+
SidebarNav,
|
|
119
|
+
SidebarNavItem,
|
|
120
|
+
SidebarNavLink,
|
|
121
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
size?: "sm" | "md" | "lg"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(
|
|
9
|
+
({ className, size = "md", ...props }, ref) => {
|
|
10
|
+
const size_classes = {
|
|
11
|
+
sm: "h-4 w-4",
|
|
12
|
+
md: "h-8 w-8",
|
|
13
|
+
lg: "h-12 w-12",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
ref={ref}
|
|
19
|
+
className={cn("inline-block", className)}
|
|
20
|
+
{...props}
|
|
21
|
+
role="status"
|
|
22
|
+
aria-label="Loading"
|
|
23
|
+
>
|
|
24
|
+
<svg
|
|
25
|
+
className={cn(
|
|
26
|
+
"animate-spin text-primary",
|
|
27
|
+
size_classes[size]
|
|
28
|
+
)}
|
|
29
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
30
|
+
fill="none"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
>
|
|
33
|
+
<circle
|
|
34
|
+
className="opacity-25"
|
|
35
|
+
cx="12"
|
|
36
|
+
cy="12"
|
|
37
|
+
r="10"
|
|
38
|
+
stroke="currentColor"
|
|
39
|
+
strokeWidth="4"
|
|
40
|
+
/>
|
|
41
|
+
<path
|
|
42
|
+
className="opacity-75"
|
|
43
|
+
fill="currentColor"
|
|
44
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
<span className="sr-only">Loading...</span>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
Spinner.displayName = "Spinner"
|
|
53
|
+
|
|
54
|
+
export { Spinner }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
export interface TextareaProps
|
|
5
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
|
6
|
+
|
|
7
|
+
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
|
+
({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
className={cn(
|
|
12
|
+
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
ref={ref}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
Textarea.displayName = "Textarea"
|
|
22
|
+
|
|
23
|
+
export { Textarea }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const TooltipProvider = TooltipPrimitive.Provider
|
|
9
|
+
|
|
10
|
+
const Tooltip = TooltipPrimitive.Root
|
|
11
|
+
|
|
12
|
+
const TooltipTrigger = TooltipPrimitive.Trigger
|
|
13
|
+
|
|
14
|
+
const TooltipContent = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
17
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
18
|
+
<TooltipPrimitive.Content
|
|
19
|
+
ref={ref}
|
|
20
|
+
sideOffset={sideOffset}
|
|
21
|
+
className={cn(
|
|
22
|
+
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
))
|
|
28
|
+
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
|
29
|
+
|
|
30
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
package/components.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "default",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.ts",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "slate",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
[emailer]
|
|
2
|
+
# Emailer module: zeptoemail_api, smtp, pop3
|
|
3
|
+
# Required: Yes
|
|
4
|
+
# Default: zeptoemail_api
|
|
5
|
+
# Description: Select the emailer module to use
|
|
6
|
+
emailer_module=zeptoemail_api
|
|
7
|
+
|
|
8
|
+
# Zeptomail API Provider Configuration
|
|
9
|
+
# Required when emailer_module=zeptoemail_api
|
|
10
|
+
# Description: API endpoint for Zeptomail email service
|
|
11
|
+
zeptomail_api_endpoint=https://api.zeptomail.com.au/v1.1/email
|
|
12
|
+
|
|
13
|
+
# Required when emailer_module=zeptoemail_api
|
|
14
|
+
# Description: Zeptomail API key for authentication
|
|
15
|
+
# SECURITY: Store this in .env.local file as ZEPTOMAIL_API_KEY (recommended)
|
|
16
|
+
# Get this from your Zeptomail account dashboard
|
|
17
|
+
# If not set in .env.local, you can set it here (not recommended for production)
|
|
18
|
+
# zeptomail_api_key=your_zeptomail_api_key
|
|
19
|
+
|
|
20
|
+
# Required: Yes
|
|
21
|
+
# Description: Default sender email address
|
|
22
|
+
# This email must be verified in your Zeptomail account
|
|
23
|
+
from_email=noreply@bookmemento.com
|
|
24
|
+
|
|
25
|
+
# Required: Yes
|
|
26
|
+
# Description: Default sender name displayed in email clients
|
|
27
|
+
from_name=Hazo Notify
|
|
28
|
+
|
|
29
|
+
# Optional: Reply-to email address
|
|
30
|
+
# Description: Email address to use for replies (if different from from_email)
|
|
31
|
+
# Default: Uses from_email if not specified
|
|
32
|
+
# reply_to_email=support@example.com
|
|
33
|
+
|
|
34
|
+
# Optional: Bounce handling email
|
|
35
|
+
# Description: Email address to receive bounce notifications
|
|
36
|
+
# bounce_email=bounce@example.com
|
|
37
|
+
|
|
38
|
+
# Optional: Return path email
|
|
39
|
+
# Description: Email address to use for return path
|
|
40
|
+
# return_path_email=returns@example.com
|
|
41
|
+
|
|
42
|
+
# SMTP Provider Configuration (placeholder - not yet implemented)
|
|
43
|
+
# Required when emailer_module=smtp
|
|
44
|
+
# Description: SMTP server hostname
|
|
45
|
+
# smtp_host=smtp.example.com
|
|
46
|
+
|
|
47
|
+
# Required when emailer_module=smtp
|
|
48
|
+
# Description: SMTP server port (typically 587 for TLS, 465 for SSL, 25 for non-secure)
|
|
49
|
+
# smtp_port=587
|
|
50
|
+
|
|
51
|
+
# Required when emailer_module=smtp
|
|
52
|
+
# Description: Use secure connection (true for SSL/TLS, false for non-secure)
|
|
53
|
+
# smtp_secure=false
|
|
54
|
+
|
|
55
|
+
# Required when emailer_module=smtp
|
|
56
|
+
# Description: SMTP authentication username (usually your email address)
|
|
57
|
+
# smtp_auth_user=your_email@example.com
|
|
58
|
+
|
|
59
|
+
# Required when emailer_module=smtp
|
|
60
|
+
# Description: SMTP authentication password or app-specific password
|
|
61
|
+
# smtp_auth_pass=your_password
|
|
62
|
+
|
|
63
|
+
# Optional: SMTP connection timeout in milliseconds
|
|
64
|
+
# Description: Timeout for SMTP connection attempts
|
|
65
|
+
# Default: 5000
|
|
66
|
+
# smtp_timeout=5000
|
|
67
|
+
|
|
68
|
+
# Optional: SMTP pool configuration
|
|
69
|
+
# Description: Enable connection pooling for better performance
|
|
70
|
+
# Default: false
|
|
71
|
+
# smtp_pool=false
|
|
72
|
+
|
|
73
|
+
# POP3 Provider Configuration (placeholder - not yet implemented)
|
|
74
|
+
# Required when emailer_module=pop3
|
|
75
|
+
# Description: POP3 server hostname
|
|
76
|
+
# pop3_host=pop3.example.com
|
|
77
|
+
|
|
78
|
+
# Required when emailer_module=pop3
|
|
79
|
+
# Description: POP3 server port (typically 995 for SSL, 110 for non-secure)
|
|
80
|
+
# pop3_port=995
|
|
81
|
+
|
|
82
|
+
# Required when emailer_module=pop3
|
|
83
|
+
# Description: Use secure connection (true for SSL, false for non-secure)
|
|
84
|
+
# pop3_secure=true
|
|
85
|
+
|
|
86
|
+
# Required when emailer_module=pop3
|
|
87
|
+
# Description: POP3 authentication username (usually your email address)
|
|
88
|
+
# pop3_auth_user=your_email@example.com
|
|
89
|
+
|
|
90
|
+
# Required when emailer_module=pop3
|
|
91
|
+
# Description: POP3 authentication password
|
|
92
|
+
# pop3_auth_pass=your_password
|
|
93
|
+
|
|
94
|
+
# Optional: POP3 connection timeout in milliseconds
|
|
95
|
+
# Description: Timeout for POP3 connection attempts
|
|
96
|
+
# Default: 5000
|
|
97
|
+
# pop3_timeout=5000
|
|
98
|
+
|
|
99
|
+
# Rate Limiting Configuration
|
|
100
|
+
# Optional: Rate limit requests per minute
|
|
101
|
+
# Description: Maximum number of requests allowed per minute per IP address
|
|
102
|
+
# Default: 10
|
|
103
|
+
# Recommended: 10-60 for production
|
|
104
|
+
# rate_limit_requests=10
|
|
105
|
+
|
|
106
|
+
# Optional: Rate limit window in seconds
|
|
107
|
+
# Description: Time window for rate limiting
|
|
108
|
+
# Default: 60 (1 minute)
|
|
109
|
+
# rate_limit_window=60
|
|
110
|
+
|
|
111
|
+
# Attachment Limits
|
|
112
|
+
# Optional: Maximum attachment size in bytes
|
|
113
|
+
# Description: Maximum size allowed per attachment (in bytes)
|
|
114
|
+
# Default: 10485760 (10MB)
|
|
115
|
+
# Recommended: 10MB for most use cases
|
|
116
|
+
# max_attachment_size=10485760
|
|
117
|
+
|
|
118
|
+
# Optional: Maximum number of attachments
|
|
119
|
+
# Description: Maximum number of attachments allowed per email
|
|
120
|
+
# Default: 10
|
|
121
|
+
# max_attachments=10
|
|
122
|
+
|
|
123
|
+
# Request Timeout
|
|
124
|
+
# Optional: Request timeout in milliseconds
|
|
125
|
+
# Description: Timeout for API requests to email providers
|
|
126
|
+
# Default: 30000 (30 seconds)
|
|
127
|
+
# request_timeout=30000
|
|
128
|
+
|
|
129
|
+
# Input Length Limits
|
|
130
|
+
# Optional: Maximum subject length in characters
|
|
131
|
+
# Description: Maximum length for email subject line
|
|
132
|
+
# Default: 255 (RFC 5322 standard)
|
|
133
|
+
# max_subject_length=255
|
|
134
|
+
|
|
135
|
+
# Optional: Maximum body length in bytes
|
|
136
|
+
# Description: Maximum size for email body content (text or HTML)
|
|
137
|
+
# Default: 1048576 (1MB)
|
|
138
|
+
# max_body_length=1048576
|
|
139
|
+
|
|
140
|
+
# CORS Configuration
|
|
141
|
+
# Optional: CORS allowed origins
|
|
142
|
+
# Description: Comma-separated list of allowed origins for CORS
|
|
143
|
+
# Default: empty (same-origin only)
|
|
144
|
+
# Use "*" to allow all origins (not recommended for production)
|
|
145
|
+
# cors_allowed_origins=
|
|
146
|
+
|
|
147
|
+
[ui]
|
|
148
|
+
# Enable UI component and all routes (e.g., /hazo_notify/emailer_test)
|
|
149
|
+
# Required: No
|
|
150
|
+
# Default: false
|
|
151
|
+
# Description: Enable the entire UI component and all routes for testing services
|
|
152
|
+
# Set to true to enable the UI, false to disable it
|
|
153
|
+
enable_ui=true
|