@webamoki/web-svelte 0.7.0 → 0.7.2
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/dist/components/form/IconInputWrapper.svelte +39 -0
- package/dist/components/form/IconInputWrapper.svelte.d.ts +20 -0
- package/dist/components/form/fields/DateField.svelte +12 -4
- package/dist/components/form/fields/DateField.svelte.d.ts +3 -1
- package/dist/components/form/fields/MessageField.svelte +19 -10
- package/dist/components/form/fields/MessageField.svelte.d.ts +2 -0
- package/dist/components/form/fields/NumberField.svelte +26 -3
- package/dist/components/form/fields/NumberField.svelte.d.ts +3 -0
- package/dist/components/form/fields/PasswordField.svelte +14 -5
- package/dist/components/form/fields/PasswordField.svelte.d.ts +3 -0
- package/dist/components/form/fields/SelectField.svelte +13 -5
- package/dist/components/form/fields/SelectField.svelte.d.ts +2 -0
- package/dist/components/form/fields/SelectMultiField.svelte +13 -5
- package/dist/components/form/fields/SelectMultiField.svelte.d.ts +2 -0
- package/dist/components/form/fields/TextField.svelte +11 -2
- package/dist/components/form/fields/TextField.svelte.d.ts +2 -0
- package/dist/components/form/fields/TextFieldNullable.svelte +22 -2
- package/dist/components/form/fields/TextFieldNullable.svelte.d.ts +2 -0
- package/dist/components/form/fields/TimeField.svelte +27 -4
- package/dist/components/form/fields/TimeField.svelte.d.ts +4 -1
- package/dist/components/form/index.d.ts +3 -2
- package/dist/components/form/index.js +3 -2
- package/dist/utils/email/README.md +209 -0
- package/dist/utils/email/index.d.ts +1 -0
- package/dist/utils/email/index.js +1 -0
- package/dist/utils/email/ses.d.ts +22 -0
- package/dist/utils/email/ses.js +95 -0
- package/package.json +10 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Component, Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
icon?: Component;
|
|
6
|
+
/**
|
|
7
|
+
* Position of the icon vertically.
|
|
8
|
+
* - 'center': Centers vertically (for single-line inputs)
|
|
9
|
+
* - 'top': Aligns to top with padding (for multi-line inputs like textarea)
|
|
10
|
+
*/
|
|
11
|
+
iconPosition?: 'center' | 'top';
|
|
12
|
+
/**
|
|
13
|
+
* Whether this wrapper should grow to fill flex container (used in layouts with buttons)
|
|
14
|
+
*/
|
|
15
|
+
flex?: boolean;
|
|
16
|
+
children: Snippet<[{ class: string }]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let { icon, iconPosition = 'center', flex = false, children }: Props = $props();
|
|
20
|
+
|
|
21
|
+
const iconClass =
|
|
22
|
+
iconPosition === 'top'
|
|
23
|
+
? 'pointer-events-none absolute top-4 left-4 text-gray-500'
|
|
24
|
+
: 'pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-gray-500';
|
|
25
|
+
|
|
26
|
+
const wrapperClass = flex ? 'relative flex-1' : 'relative';
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
{#if icon}
|
|
30
|
+
{@const Icon = icon}
|
|
31
|
+
<div class={wrapperClass}>
|
|
32
|
+
<div class={iconClass}>
|
|
33
|
+
<Icon class="size-5" />
|
|
34
|
+
</div>
|
|
35
|
+
{@render children({ class: 'pl-12' })}
|
|
36
|
+
</div>
|
|
37
|
+
{:else}
|
|
38
|
+
{@render children({ class: '' })}
|
|
39
|
+
{/if}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Component, Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
icon?: Component;
|
|
4
|
+
/**
|
|
5
|
+
* Position of the icon vertically.
|
|
6
|
+
* - 'center': Centers vertically (for single-line inputs)
|
|
7
|
+
* - 'top': Aligns to top with padding (for multi-line inputs like textarea)
|
|
8
|
+
*/
|
|
9
|
+
iconPosition?: 'center' | 'top';
|
|
10
|
+
/**
|
|
11
|
+
* Whether this wrapper should grow to fill flex container (used in layouts with buttons)
|
|
12
|
+
*/
|
|
13
|
+
flex?: boolean;
|
|
14
|
+
children: Snippet<[{
|
|
15
|
+
class: string;
|
|
16
|
+
}]>;
|
|
17
|
+
}
|
|
18
|
+
declare const IconInputWrapper: Component<Props, {}, "">;
|
|
19
|
+
type IconInputWrapper = ReturnType<typeof IconInputWrapper>;
|
|
20
|
+
export default IconInputWrapper;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
-
import
|
|
3
|
-
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
4
3
|
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
5
|
import { CalendarDate } from '@internationalized/date';
|
|
6
|
+
import type { Component } from 'svelte';
|
|
7
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
8
|
+
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
6
9
|
|
|
7
10
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
8
11
|
value?: CalendarDate;
|
|
9
12
|
class?: string;
|
|
13
|
+
icon?: Component;
|
|
10
14
|
}
|
|
11
|
-
let { value = $bindable(), class: className, ...fieldProps }: Props = $props();
|
|
15
|
+
let { value = $bindable(), class: className, icon, ...fieldProps }: Props = $props();
|
|
12
16
|
|
|
13
17
|
// Getter: format CalendarDate → string (YYYY-MM-DD)
|
|
14
18
|
function get(): string {
|
|
@@ -34,6 +38,10 @@
|
|
|
34
38
|
|
|
35
39
|
<FieldWrapper {...fieldProps}>
|
|
36
40
|
{#snippet formElem(props)}
|
|
37
|
-
<
|
|
41
|
+
<IconInputWrapper {icon}>
|
|
42
|
+
{#snippet children({ class: iconClass })}
|
|
43
|
+
<Input type="date" bind:value={get, set} class={cn(iconClass, className)} {...props} />
|
|
44
|
+
{/snippet}
|
|
45
|
+
</IconInputWrapper>
|
|
38
46
|
{/snippet}
|
|
39
47
|
</FieldWrapper>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { CalendarDate } from '@internationalized/date';
|
|
2
|
+
import type { Component } from 'svelte';
|
|
1
3
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
4
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
|
-
import { CalendarDate } from '@internationalized/date';
|
|
4
5
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
5
6
|
props: FieldWrapperProps<T, U, M> & {
|
|
6
7
|
value?: CalendarDate;
|
|
7
8
|
class?: string;
|
|
9
|
+
icon?: Component;
|
|
8
10
|
};
|
|
9
11
|
exports: {};
|
|
10
12
|
bindings: "value";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
-
import
|
|
3
|
-
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
4
3
|
import { Textarea } from '../../../shadcn/components/ui/textarea/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
5
|
import { Lock, LockOpen } from '@lucide/svelte';
|
|
6
|
+
import type { Component } from 'svelte';
|
|
7
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
8
|
+
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
6
9
|
|
|
7
10
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
8
11
|
value?: string;
|
|
@@ -11,6 +14,7 @@
|
|
|
11
14
|
defaultHeight?: number;
|
|
12
15
|
showLock?: boolean;
|
|
13
16
|
defaultLocked?: boolean;
|
|
17
|
+
icon?: Component;
|
|
14
18
|
}
|
|
15
19
|
let {
|
|
16
20
|
value = $bindable(),
|
|
@@ -19,6 +23,7 @@
|
|
|
19
23
|
defaultHeight = 100,
|
|
20
24
|
showLock = true,
|
|
21
25
|
defaultLocked = false,
|
|
26
|
+
icon,
|
|
22
27
|
...fieldProps
|
|
23
28
|
}: Props = $props();
|
|
24
29
|
|
|
@@ -28,14 +33,18 @@
|
|
|
28
33
|
<FieldWrapper {...fieldProps}>
|
|
29
34
|
{#snippet formElem(props)}
|
|
30
35
|
<div class="flex w-full items-start gap-2">
|
|
31
|
-
<!-- Textarea itself -->
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
<!-- Textarea itself with optional left icon -->
|
|
37
|
+
<IconInputWrapper {icon} iconPosition="top" flex>
|
|
38
|
+
{#snippet children({ class: iconClass })}
|
|
39
|
+
<Textarea
|
|
40
|
+
bind:value
|
|
41
|
+
class={cn(iconClass, className || '', locked ? 'resize-none' : 'resize-y')}
|
|
42
|
+
{placeholder}
|
|
43
|
+
style={defaultHeight ? `height: ${defaultHeight}px` : undefined}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
{/snippet}
|
|
47
|
+
</IconInputWrapper>
|
|
39
48
|
|
|
40
49
|
<!-- Lock/unlock button -->
|
|
41
50
|
{#if showLock}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -8,6 +9,7 @@ declare function $$render<T extends Record<string, unknown>, U extends FormPath<
|
|
|
8
9
|
defaultHeight?: number;
|
|
9
10
|
showLock?: boolean;
|
|
10
11
|
defaultLocked?: boolean;
|
|
12
|
+
icon?: Component;
|
|
11
13
|
};
|
|
12
14
|
exports: {};
|
|
13
15
|
bindings: "value";
|
|
@@ -1,18 +1,41 @@
|
|
|
1
1
|
<script lang="ts" generics="V,T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
3
|
+
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
|
+
import type { Component } from 'svelte';
|
|
2
6
|
import type { FormPath } from 'sveltekit-superforms';
|
|
3
7
|
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
4
|
-
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
5
8
|
|
|
6
9
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
7
10
|
value?: V;
|
|
8
11
|
class?: string;
|
|
9
12
|
step?: HTMLInputElement['step'];
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
icon?: Component;
|
|
10
15
|
}
|
|
11
|
-
let {
|
|
16
|
+
let {
|
|
17
|
+
value = $bindable(),
|
|
18
|
+
class: className,
|
|
19
|
+
step,
|
|
20
|
+
placeholder,
|
|
21
|
+
icon,
|
|
22
|
+
...fieldProps
|
|
23
|
+
}: Props = $props();
|
|
12
24
|
</script>
|
|
13
25
|
|
|
14
26
|
<FieldWrapper {...fieldProps}>
|
|
15
27
|
{#snippet formElem(props)}
|
|
16
|
-
<
|
|
28
|
+
<IconInputWrapper {icon}>
|
|
29
|
+
{#snippet children({ class: iconClass })}
|
|
30
|
+
<Input
|
|
31
|
+
type="number"
|
|
32
|
+
bind:value
|
|
33
|
+
class={cn(iconClass, className)}
|
|
34
|
+
{placeholder}
|
|
35
|
+
{step}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
{/snippet}
|
|
39
|
+
</IconInputWrapper>
|
|
17
40
|
{/snippet}
|
|
18
41
|
</FieldWrapper>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<V, T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -5,6 +6,8 @@ declare function $$render<V, T extends Record<string, unknown>, U extends FormPa
|
|
|
5
6
|
value?: V;
|
|
6
7
|
class?: string;
|
|
7
8
|
step?: HTMLInputElement["step"];
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
icon?: Component;
|
|
8
11
|
};
|
|
9
12
|
exports: {};
|
|
10
13
|
bindings: "value";
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
-
import
|
|
3
|
-
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
4
3
|
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
5
|
import { Eye, EyeOff } from '@lucide/svelte';
|
|
6
|
+
import type { Component } from 'svelte';
|
|
7
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
8
|
+
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
6
9
|
|
|
7
10
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
8
11
|
value?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
icon?: Component;
|
|
9
14
|
}
|
|
10
|
-
let { value = $bindable(), ...fieldProps }: Props = $props();
|
|
15
|
+
let { value = $bindable(), class: className, icon, ...fieldProps }: Props = $props();
|
|
11
16
|
|
|
12
17
|
let show = $state(false);
|
|
13
18
|
let inputType = $derived(show ? 'text' : 'password');
|
|
@@ -16,8 +21,12 @@
|
|
|
16
21
|
<FieldWrapper {...fieldProps}>
|
|
17
22
|
{#snippet formElem(props)}
|
|
18
23
|
<div class="flex w-full items-stretch gap-2">
|
|
19
|
-
<!-- Input itself -->
|
|
20
|
-
<
|
|
24
|
+
<!-- Input itself with optional left icon -->
|
|
25
|
+
<IconInputWrapper {icon} flex>
|
|
26
|
+
{#snippet children({ class: iconClass })}
|
|
27
|
+
<Input type={inputType} bind:value class={cn(iconClass, className)} {...props} />
|
|
28
|
+
{/snippet}
|
|
29
|
+
</IconInputWrapper>
|
|
21
30
|
|
|
22
31
|
<!-- Show/hide button outside input -->
|
|
23
32
|
<button
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
4
5
|
props: FieldWrapperProps<T, U, M> & {
|
|
5
6
|
value?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
icon?: Component;
|
|
6
9
|
};
|
|
7
10
|
exports: {};
|
|
8
11
|
bindings: "value";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
lang="ts"
|
|
3
3
|
generics="I,V, K extends string | number,T extends Record<string, unknown>, U extends FormPath<T>, M"
|
|
4
4
|
>
|
|
5
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
5
6
|
import {
|
|
6
7
|
Select,
|
|
7
8
|
SelectContent,
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
SelectTrigger
|
|
12
13
|
} from '../../../shadcn/components/ui/select/index.js';
|
|
13
14
|
import { cn } from '../../../shadcn/utils.js';
|
|
15
|
+
import type { Component } from 'svelte';
|
|
14
16
|
import type { FormPath } from 'sveltekit-superforms';
|
|
15
17
|
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
16
18
|
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
onchange?: (value: V | undefined) => void;
|
|
24
26
|
class?: string;
|
|
25
27
|
placeholder: string;
|
|
28
|
+
icon?: Component;
|
|
26
29
|
}
|
|
27
30
|
let {
|
|
28
31
|
value = $bindable(undefined),
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
onchange,
|
|
34
37
|
placeholder,
|
|
35
38
|
items,
|
|
39
|
+
icon,
|
|
36
40
|
...fieldProps
|
|
37
41
|
}: Props = $props();
|
|
38
42
|
let valueToItem: Map<V, I> = new Map(items.map((item) => [getValue(item), item] as const));
|
|
@@ -63,11 +67,15 @@
|
|
|
63
67
|
<FieldWrapper {...fieldProps}>
|
|
64
68
|
{#snippet formElem(props)}
|
|
65
69
|
<Select type="single" {...props} bind:value={getKeyFromValue, setValueFromKey}>
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
<IconInputWrapper {icon}>
|
|
71
|
+
{#snippet children({ class: iconClass })}
|
|
72
|
+
<SelectTrigger class={cn('w-full cursor-pointer truncate', iconClass, className)}>
|
|
73
|
+
<span class="block truncate">
|
|
74
|
+
{value ? getLabel(valueToItem.get(value)!) : placeholder}
|
|
75
|
+
</span>
|
|
76
|
+
</SelectTrigger>
|
|
77
|
+
{/snippet}
|
|
78
|
+
</IconInputWrapper>
|
|
71
79
|
<SelectContent>
|
|
72
80
|
<SelectGroup>
|
|
73
81
|
<SelectLabel>{fieldProps.label}</SelectLabel>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<I, V, K extends string | number, T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -10,6 +11,7 @@ declare function $$render<I, V, K extends string | number, T extends Record<stri
|
|
|
10
11
|
onchange?: (value: V | undefined) => void;
|
|
11
12
|
class?: string;
|
|
12
13
|
placeholder: string;
|
|
14
|
+
icon?: Component;
|
|
13
15
|
};
|
|
14
16
|
exports: {};
|
|
15
17
|
bindings: "value";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
lang="ts"
|
|
3
3
|
generics="I,V, K extends string | number,T extends Record<string, unknown>, U extends FormPath<T>, M"
|
|
4
4
|
>
|
|
5
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
5
6
|
import {
|
|
6
7
|
Select,
|
|
7
8
|
SelectContent,
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
SelectTrigger
|
|
12
13
|
} from '../../../shadcn/components/ui/select/index.js';
|
|
13
14
|
import { cn } from '../../../shadcn/utils.js';
|
|
15
|
+
import type { Component } from 'svelte';
|
|
14
16
|
import type { FormPath } from 'sveltekit-superforms';
|
|
15
17
|
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
16
18
|
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
onchange?: (value: V[]) => void;
|
|
24
26
|
class?: string;
|
|
25
27
|
placeholder: string;
|
|
28
|
+
icon?: Component;
|
|
26
29
|
}
|
|
27
30
|
let {
|
|
28
31
|
values = $bindable([]),
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
onchange,
|
|
34
37
|
placeholder,
|
|
35
38
|
items,
|
|
39
|
+
icon,
|
|
36
40
|
...fieldProps
|
|
37
41
|
}: Props = $props();
|
|
38
42
|
let valueToItem: Map<V, I> = new Map(items.map((item) => [getValue(item), item] as const));
|
|
@@ -70,11 +74,15 @@
|
|
|
70
74
|
<FieldWrapper {...fieldProps}>
|
|
71
75
|
{#snippet formElem(props)}
|
|
72
76
|
<Select type="multiple" {...props} bind:value={getKeyFromValue, setValueFromKey}>
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
<IconInputWrapper {icon}>
|
|
78
|
+
{#snippet children({ class: iconClass })}
|
|
79
|
+
<SelectTrigger class={cn('w-full cursor-pointer truncate', iconClass, className)}>
|
|
80
|
+
<span class="block truncate">
|
|
81
|
+
{getPreview()}
|
|
82
|
+
</span>
|
|
83
|
+
</SelectTrigger>
|
|
84
|
+
{/snippet}
|
|
85
|
+
</IconInputWrapper>
|
|
78
86
|
<SelectContent>
|
|
79
87
|
<SelectGroup>
|
|
80
88
|
<SelectLabel>{fieldProps.label}</SelectLabel>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<I, V, K extends string | number, T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -10,6 +11,7 @@ declare function $$render<I, V, K extends string | number, T extends Record<stri
|
|
|
10
11
|
onchange?: (value: V[]) => void;
|
|
11
12
|
class?: string;
|
|
12
13
|
placeholder: string;
|
|
14
|
+
icon?: Component;
|
|
13
15
|
};
|
|
14
16
|
exports: {};
|
|
15
17
|
bindings: "values";
|
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
3
|
+
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
|
+
import type { Component } from 'svelte';
|
|
2
6
|
import type { FormPath } from 'sveltekit-superforms';
|
|
3
7
|
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
4
|
-
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
5
8
|
|
|
6
9
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
7
10
|
value?: string;
|
|
8
11
|
type?: HTMLInputElement['type'];
|
|
9
12
|
class?: string;
|
|
10
13
|
placeholder?: string;
|
|
14
|
+
icon?: Component;
|
|
11
15
|
}
|
|
12
16
|
let {
|
|
13
17
|
value = $bindable(),
|
|
14
18
|
type = 'text',
|
|
15
19
|
class: className,
|
|
16
20
|
placeholder,
|
|
21
|
+
icon,
|
|
17
22
|
...fieldProps
|
|
18
23
|
}: Props = $props();
|
|
19
24
|
</script>
|
|
20
25
|
|
|
21
26
|
<FieldWrapper {...fieldProps}>
|
|
22
27
|
{#snippet formElem(props)}
|
|
23
|
-
<
|
|
28
|
+
<IconInputWrapper {icon}>
|
|
29
|
+
{#snippet children({ class: iconClass })}
|
|
30
|
+
<Input {type} bind:value class={cn(iconClass, className)} {placeholder} {...props} />
|
|
31
|
+
{/snippet}
|
|
32
|
+
</IconInputWrapper>
|
|
24
33
|
{/snippet}
|
|
25
34
|
</FieldWrapper>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -6,6 +7,7 @@ declare function $$render<T extends Record<string, unknown>, U extends FormPath<
|
|
|
6
7
|
type?: HTMLInputElement["type"];
|
|
7
8
|
class?: string;
|
|
8
9
|
placeholder?: string;
|
|
10
|
+
icon?: Component;
|
|
9
11
|
};
|
|
10
12
|
exports: {};
|
|
11
13
|
bindings: "value";
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
+
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
3
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
4
|
+
import type { Component } from 'svelte';
|
|
2
5
|
import type { FormPath } from 'sveltekit-superforms';
|
|
3
6
|
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
4
|
-
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
5
7
|
|
|
6
8
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
7
9
|
value?: string | null;
|
|
8
10
|
type?: HTMLInputElement['type'];
|
|
9
11
|
class?: string;
|
|
10
12
|
placeholder?: string;
|
|
13
|
+
icon?: Component;
|
|
11
14
|
}
|
|
12
15
|
let {
|
|
13
16
|
value = $bindable(),
|
|
14
17
|
type = 'text',
|
|
15
18
|
class: className,
|
|
16
19
|
placeholder,
|
|
20
|
+
icon,
|
|
17
21
|
...fieldProps
|
|
18
22
|
}: Props = $props();
|
|
19
23
|
|
|
@@ -28,6 +32,22 @@
|
|
|
28
32
|
|
|
29
33
|
<FieldWrapper {...fieldProps}>
|
|
30
34
|
{#snippet formElem(props)}
|
|
31
|
-
|
|
35
|
+
{#if icon}
|
|
36
|
+
{@const Icon = icon}
|
|
37
|
+
<div class="relative">
|
|
38
|
+
<div class="pointer-events-none absolute top-1/2 left-4 -translate-y-1/2 text-gray-500">
|
|
39
|
+
<Icon class="size-5" />
|
|
40
|
+
</div>
|
|
41
|
+
<Input
|
|
42
|
+
{type}
|
|
43
|
+
bind:value={get, set}
|
|
44
|
+
class={cn('pl-12', className)}
|
|
45
|
+
{placeholder}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
{:else}
|
|
50
|
+
<Input {type} bind:value={get, set} class={className} {placeholder} {...props} />
|
|
51
|
+
{/if}
|
|
32
52
|
{/snippet}
|
|
33
53
|
</FieldWrapper>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
1
2
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
3
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
4
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
@@ -6,6 +7,7 @@ declare function $$render<T extends Record<string, unknown>, U extends FormPath<
|
|
|
6
7
|
type?: HTMLInputElement["type"];
|
|
7
8
|
class?: string;
|
|
8
9
|
placeholder?: string;
|
|
10
|
+
icon?: Component;
|
|
9
11
|
};
|
|
10
12
|
exports: {};
|
|
11
13
|
bindings: "value";
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>, M">
|
|
2
|
-
import
|
|
3
|
-
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
2
|
+
import IconInputWrapper from '../IconInputWrapper.svelte';
|
|
4
3
|
import { Input } from '../../../shadcn/components/ui/input/index.js';
|
|
4
|
+
import { cn } from '../../../shadcn/utils.js';
|
|
5
5
|
import { Time } from '@internationalized/date';
|
|
6
|
+
import type { Component } from 'svelte';
|
|
7
|
+
import type { FormPath } from 'sveltekit-superforms';
|
|
8
|
+
import FieldWrapper, { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
6
9
|
|
|
7
10
|
interface Props extends FieldWrapperProps<T, U, M> {
|
|
8
11
|
value?: Time;
|
|
9
12
|
class?: string;
|
|
10
13
|
step?: HTMLInputElement['step'];
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
icon?: Component;
|
|
11
16
|
}
|
|
12
|
-
let {
|
|
17
|
+
let {
|
|
18
|
+
value = $bindable(),
|
|
19
|
+
class: className,
|
|
20
|
+
step,
|
|
21
|
+
placeholder,
|
|
22
|
+
icon,
|
|
23
|
+
...fieldProps
|
|
24
|
+
}: Props = $props();
|
|
13
25
|
// Getter: format Time as string depending on step
|
|
14
26
|
function get(): string {
|
|
15
27
|
if (!value) return '00:00:00';
|
|
@@ -44,6 +56,17 @@
|
|
|
44
56
|
|
|
45
57
|
<FieldWrapper {...fieldProps}>
|
|
46
58
|
{#snippet formElem(props)}
|
|
47
|
-
<
|
|
59
|
+
<IconInputWrapper {icon}>
|
|
60
|
+
{#snippet children({ class: iconClass })}
|
|
61
|
+
<Input
|
|
62
|
+
type="time"
|
|
63
|
+
bind:value={get, set}
|
|
64
|
+
class={cn(iconClass, className)}
|
|
65
|
+
{placeholder}
|
|
66
|
+
{step}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
{/snippet}
|
|
70
|
+
</IconInputWrapper>
|
|
48
71
|
{/snippet}
|
|
49
72
|
</FieldWrapper>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { Time } from '@internationalized/date';
|
|
2
|
+
import type { Component } from 'svelte';
|
|
1
3
|
import type { FormPath } from 'sveltekit-superforms';
|
|
2
4
|
import { type FieldWrapperProps } from '../FieldWrapper.svelte';
|
|
3
|
-
import { Time } from '@internationalized/date';
|
|
4
5
|
declare function $$render<T extends Record<string, unknown>, U extends FormPath<T>, M>(): {
|
|
5
6
|
props: FieldWrapperProps<T, U, M> & {
|
|
6
7
|
value?: Time;
|
|
7
8
|
class?: string;
|
|
8
9
|
step?: HTMLInputElement["step"];
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
icon?: Component;
|
|
9
12
|
};
|
|
10
13
|
exports: {};
|
|
11
14
|
bindings: "value";
|
|
@@ -3,15 +3,16 @@ import ChoiceField from './fields/ChoiceField.svelte';
|
|
|
3
3
|
import ChoiceMultiField from './fields/ChoiceMultiField.svelte';
|
|
4
4
|
import DateField from './fields/DateField.svelte';
|
|
5
5
|
import HexColorField from './fields/HexColorField.svelte';
|
|
6
|
+
import MessageField from './fields/MessageField.svelte';
|
|
6
7
|
import NumberField from './fields/NumberField.svelte';
|
|
7
8
|
import PasswordField from './fields/PasswordField.svelte';
|
|
8
9
|
import SelectField from './fields/SelectField.svelte';
|
|
9
10
|
import SelectMultiField from './fields/SelectMultiField.svelte';
|
|
10
11
|
import TextField from './fields/TextField.svelte';
|
|
11
12
|
import TextFieldNullable from './fields/TextFieldNullable.svelte';
|
|
12
|
-
import MessageField from './fields/MessageField.svelte';
|
|
13
13
|
import TimeField from './fields/TimeField.svelte';
|
|
14
14
|
import WeekdayChoiceField from './fields/WeekdayChoiceField.svelte';
|
|
15
15
|
import WeekdayChoiceMultiField from './fields/WeekdayChoiceMultiField.svelte';
|
|
16
16
|
import Form from './Form.svelte';
|
|
17
|
-
|
|
17
|
+
import IconInputWrapper from './IconInputWrapper.svelte';
|
|
18
|
+
export { Button, ChoiceField, ChoiceMultiField, DateField, Form, HexColorField, IconInputWrapper, MessageField, NumberField, PasswordField, SelectField, SelectMultiField, TextField, TextFieldNullable, TimeField, WeekdayChoiceField, WeekdayChoiceMultiField };
|
|
@@ -3,15 +3,16 @@ import ChoiceField from './fields/ChoiceField.svelte';
|
|
|
3
3
|
import ChoiceMultiField from './fields/ChoiceMultiField.svelte';
|
|
4
4
|
import DateField from './fields/DateField.svelte';
|
|
5
5
|
import HexColorField from './fields/HexColorField.svelte';
|
|
6
|
+
import MessageField from './fields/MessageField.svelte';
|
|
6
7
|
import NumberField from './fields/NumberField.svelte';
|
|
7
8
|
import PasswordField from './fields/PasswordField.svelte';
|
|
8
9
|
import SelectField from './fields/SelectField.svelte';
|
|
9
10
|
import SelectMultiField from './fields/SelectMultiField.svelte';
|
|
10
11
|
import TextField from './fields/TextField.svelte';
|
|
11
12
|
import TextFieldNullable from './fields/TextFieldNullable.svelte';
|
|
12
|
-
import MessageField from './fields/MessageField.svelte';
|
|
13
13
|
import TimeField from './fields/TimeField.svelte';
|
|
14
14
|
import WeekdayChoiceField from './fields/WeekdayChoiceField.svelte';
|
|
15
15
|
import WeekdayChoiceMultiField from './fields/WeekdayChoiceMultiField.svelte';
|
|
16
16
|
import Form from './Form.svelte';
|
|
17
|
-
|
|
17
|
+
import IconInputWrapper from './IconInputWrapper.svelte';
|
|
18
|
+
export { Button, ChoiceField, ChoiceMultiField, DateField, Form, HexColorField, IconInputWrapper, MessageField, NumberField, PasswordField, SelectField, SelectMultiField, TextField, TextFieldNullable, TimeField, WeekdayChoiceField, WeekdayChoiceMultiField };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Email Utility Package
|
|
2
|
+
|
|
3
|
+
This package provides utilities for sending emails using AWS Simple Email Service (SES).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The AWS SES SDK is already included as a dependency. You'll need to configure your AWS credentials and region.
|
|
8
|
+
|
|
9
|
+
## Configuration
|
|
10
|
+
|
|
11
|
+
### Environment Variables
|
|
12
|
+
|
|
13
|
+
The email utility requires the following environment variables:
|
|
14
|
+
|
|
15
|
+
- `AWS_REGION` - AWS region where SES is configured (e.g., `us-east-1`)
|
|
16
|
+
- `AWS_ACCESS_KEY_ID` - AWS access key ID
|
|
17
|
+
- `AWS_SECRET_ACCESS_KEY` - AWS secret access key
|
|
18
|
+
|
|
19
|
+
### SES Setup
|
|
20
|
+
|
|
21
|
+
Before using this utility, ensure:
|
|
22
|
+
|
|
23
|
+
1. Your AWS account has SES enabled in your chosen region
|
|
24
|
+
2. Your sender email address(es) are verified in SES
|
|
25
|
+
3. If in SES sandbox mode, recipient addresses must also be verified
|
|
26
|
+
4. Your account has the necessary IAM permissions to send emails via SES
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Basic Example
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { sendEmail } from '@webamoki/web-svelte/utils/email';
|
|
34
|
+
|
|
35
|
+
// Send a simple text email
|
|
36
|
+
const messageId = await sendEmail({
|
|
37
|
+
to: 'recipient@example.com',
|
|
38
|
+
subject: 'Hello from SES',
|
|
39
|
+
text: 'This is a plain text email.',
|
|
40
|
+
from: 'sender@example.com'
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log('Email sent with message ID:', messageId);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### HTML Email
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
await sendEmail({
|
|
50
|
+
to: 'recipient@example.com',
|
|
51
|
+
subject: 'Welcome!',
|
|
52
|
+
html: '<h1>Welcome to our service!</h1><p>Thanks for signing up.</p>',
|
|
53
|
+
text: 'Welcome to our service! Thanks for signing up.', // Fallback for email clients that don't support HTML
|
|
54
|
+
from: 'noreply@example.com',
|
|
55
|
+
fromName: 'My Application'
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Multiple Recipients
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
await sendEmail({
|
|
63
|
+
to: ['user1@example.com', 'user2@example.com'],
|
|
64
|
+
cc: 'manager@example.com',
|
|
65
|
+
bcc: ['archive@example.com', 'backup@example.com'],
|
|
66
|
+
subject: 'Team Update',
|
|
67
|
+
text: 'This is a team-wide announcement.',
|
|
68
|
+
from: 'team@example.com'
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### With Reply-To
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
await sendEmail({
|
|
76
|
+
to: 'customer@example.com',
|
|
77
|
+
subject: 'Your order confirmation',
|
|
78
|
+
html: '<p>Your order has been confirmed!</p>',
|
|
79
|
+
from: 'noreply@example.com',
|
|
80
|
+
fromName: 'Order System',
|
|
81
|
+
replyTo: 'support@example.com'
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
### `sendEmail(options: SendEmailOptions): Promise<string>`
|
|
88
|
+
|
|
89
|
+
Sends an email via AWS SES.
|
|
90
|
+
|
|
91
|
+
**Returns:** Promise that resolves to the SES message ID (string) on success.
|
|
92
|
+
|
|
93
|
+
**Throws:** Error with descriptive message if:
|
|
94
|
+
|
|
95
|
+
- Validation fails (missing required fields, invalid recipients, etc.)
|
|
96
|
+
- AWS SES returns an error (credentials, permissions, service issues)
|
|
97
|
+
- SES response does not contain a MessageId (unlikely but handled explicitly)
|
|
98
|
+
|
|
99
|
+
### `SendEmailOptions`
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface SendEmailOptions {
|
|
103
|
+
to: string | string[]; // Required: recipient email address(es)
|
|
104
|
+
cc?: string | string[]; // Optional: CC recipients
|
|
105
|
+
bcc?: string | string[]; // Optional: BCC recipients
|
|
106
|
+
subject: string; // Required: email subject
|
|
107
|
+
text?: string; // Optional: plain text body
|
|
108
|
+
html?: string; // Optional: HTML body
|
|
109
|
+
from: string; // Required: sender email
|
|
110
|
+
fromName?: string; // Optional: sender display name
|
|
111
|
+
replyTo?: string | string[]; // Optional: reply-to address(es)
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
The `sendEmail` function validates inputs and will throw errors for:
|
|
118
|
+
|
|
119
|
+
- Missing required fields (`to`, `from`, `subject`, at least one of `text` or `html`)
|
|
120
|
+
- Empty recipient list (no valid email addresses after filtering)
|
|
121
|
+
- AWS SES errors (credentials, permissions, service issues)
|
|
122
|
+
|
|
123
|
+
**Note:** The function automatically filters out empty strings and whitespace-only values from recipient arrays (`to`, `cc`, `bcc`, `replyTo`). For example, `['', 'valid@example.com', ' ']` will be processed as `['valid@example.com']`.
|
|
124
|
+
|
|
125
|
+
Always wrap calls in try-catch:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
try {
|
|
129
|
+
const messageId = await sendEmail({
|
|
130
|
+
to: 'user@example.com',
|
|
131
|
+
subject: 'Test',
|
|
132
|
+
text: 'Hello!',
|
|
133
|
+
from: 'sender@example.com'
|
|
134
|
+
});
|
|
135
|
+
console.log('Success:', messageId);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error('Failed to send email:', error.message);
|
|
138
|
+
// Handle error appropriately
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## SvelteKit Integration
|
|
143
|
+
|
|
144
|
+
### Server-Only Usage
|
|
145
|
+
|
|
146
|
+
⚠️ **Important:** This utility should only be used in server-side code (e.g., `+page.server.ts`, `+server.ts`, or server-side functions) as it requires AWS credentials.
|
|
147
|
+
|
|
148
|
+
### Example: Contact Form Handler
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// src/routes/contact/+page.server.ts
|
|
152
|
+
import { sendEmail } from '@webamoki/web-svelte/utils/email';
|
|
153
|
+
import type { Actions } from './$types';
|
|
154
|
+
|
|
155
|
+
export const actions: Actions = {
|
|
156
|
+
default: async ({ request }) => {
|
|
157
|
+
const formData = await request.formData();
|
|
158
|
+
const email = formData.get('email') as string;
|
|
159
|
+
const message = formData.get('message') as string;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await sendEmail({
|
|
163
|
+
to: 'admin@example.com',
|
|
164
|
+
subject: `Contact form submission from ${email}`,
|
|
165
|
+
text: message,
|
|
166
|
+
from: 'noreply@example.com',
|
|
167
|
+
replyTo: email
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return { success: true };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error('Email error:', error);
|
|
173
|
+
return { success: false, error: 'Failed to send message' };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Best Practices
|
|
180
|
+
|
|
181
|
+
1. **Never expose AWS credentials in client-side code**
|
|
182
|
+
2. **Use environment variables for configuration**
|
|
183
|
+
3. **Verify sender addresses in SES before deploying**
|
|
184
|
+
4. **Always provide both `text` and `html` versions when sending HTML emails**
|
|
185
|
+
5. **Handle errors gracefully and log failures for monitoring**
|
|
186
|
+
6. **Consider rate limits** - SES has sending limits based on your account status
|
|
187
|
+
7. **Use BCC for bulk emails** to avoid exposing all recipients
|
|
188
|
+
8. **Set appropriate Reply-To addresses** for better user experience
|
|
189
|
+
|
|
190
|
+
## Troubleshooting
|
|
191
|
+
|
|
192
|
+
### "Sender not verified" error
|
|
193
|
+
|
|
194
|
+
Verify your sender email address in the AWS SES console for your region.
|
|
195
|
+
|
|
196
|
+
### "Access Denied" error
|
|
197
|
+
|
|
198
|
+
Ensure your AWS credentials have the `ses:SendEmail` permission.
|
|
199
|
+
|
|
200
|
+
### Emails not arriving
|
|
201
|
+
|
|
202
|
+
- Check SES sending statistics in AWS console
|
|
203
|
+
- Verify you're not in sandbox mode (or that recipients are verified)
|
|
204
|
+
- Check spam folders
|
|
205
|
+
- Review bounce and complaint notifications in SES
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendEmail, type SendEmailOptions } from './ses.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { sendEmail } from './ses.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface SendEmailOptions {
|
|
2
|
+
to: string | string[];
|
|
3
|
+
cc?: string | string[];
|
|
4
|
+
bcc?: string | string[];
|
|
5
|
+
subject: string;
|
|
6
|
+
text?: string;
|
|
7
|
+
html?: string;
|
|
8
|
+
from: string;
|
|
9
|
+
fromName?: string;
|
|
10
|
+
replyTo?: string | string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Send an email using AWS SES.
|
|
14
|
+
*
|
|
15
|
+
* Environment variables required:
|
|
16
|
+
* - AWS_REGION
|
|
17
|
+
* - AWS_ACCESS_KEY_ID
|
|
18
|
+
* - AWS_SECRET_ACCESS_KEY
|
|
19
|
+
*
|
|
20
|
+
* @returns messageId returned by SES
|
|
21
|
+
*/
|
|
22
|
+
export declare function sendEmail(options: SendEmailOptions): Promise<string>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
2
|
+
// Create SES client once at module level for reuse across function calls
|
|
3
|
+
const sesClient = new SESClient({ region: process.env.AWS_REGION });
|
|
4
|
+
/**
|
|
5
|
+
* Send an email using AWS SES.
|
|
6
|
+
*
|
|
7
|
+
* Environment variables required:
|
|
8
|
+
* - AWS_REGION
|
|
9
|
+
* - AWS_ACCESS_KEY_ID
|
|
10
|
+
* - AWS_SECRET_ACCESS_KEY
|
|
11
|
+
*
|
|
12
|
+
* @returns messageId returned by SES
|
|
13
|
+
*/
|
|
14
|
+
export async function sendEmail(options) {
|
|
15
|
+
if (!options)
|
|
16
|
+
throw new Error('sendEmail: options is required');
|
|
17
|
+
const { to, cc, bcc, subject, text, html, from, fromName, replyTo } = options;
|
|
18
|
+
if (!subject) {
|
|
19
|
+
throw new Error('sendEmail: subject is required');
|
|
20
|
+
}
|
|
21
|
+
if (!text && !html) {
|
|
22
|
+
throw new Error('sendEmail: at least one of text or html body must be provided');
|
|
23
|
+
}
|
|
24
|
+
if (!from) {
|
|
25
|
+
throw new Error('sendEmail: sender `from` is required');
|
|
26
|
+
}
|
|
27
|
+
// Normalize and validate addresses: convert to array and filter out empty/whitespace strings
|
|
28
|
+
const normalizeAddresses = (addr) => {
|
|
29
|
+
if (addr === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
const addresses = Array.isArray(addr) ? addr : [addr];
|
|
32
|
+
const filtered = addresses.filter((a) => a && a.trim() !== '');
|
|
33
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
34
|
+
};
|
|
35
|
+
const toAddresses = normalizeAddresses(to);
|
|
36
|
+
const ccAddresses = normalizeAddresses(cc);
|
|
37
|
+
const bccAddresses = normalizeAddresses(bcc);
|
|
38
|
+
const replyToAddresses = normalizeAddresses(replyTo);
|
|
39
|
+
if (!toAddresses || toAddresses.length === 0) {
|
|
40
|
+
throw new Error('sendEmail: at least one valid recipient is required (to)');
|
|
41
|
+
}
|
|
42
|
+
// Format source with optional fromName
|
|
43
|
+
const source = fromName ? `${fromName} <${from}>` : from;
|
|
44
|
+
// Build message body
|
|
45
|
+
const Message = {
|
|
46
|
+
Subject: {
|
|
47
|
+
Charset: 'UTF-8',
|
|
48
|
+
Data: subject
|
|
49
|
+
},
|
|
50
|
+
Body: {}
|
|
51
|
+
};
|
|
52
|
+
if (html) {
|
|
53
|
+
Message.Body.Html = {
|
|
54
|
+
Charset: 'UTF-8',
|
|
55
|
+
Data: html
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (text) {
|
|
59
|
+
Message.Body.Text = {
|
|
60
|
+
Charset: 'UTF-8',
|
|
61
|
+
Data: text
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const params = {
|
|
65
|
+
Source: source,
|
|
66
|
+
Destination: {
|
|
67
|
+
ToAddresses: toAddresses,
|
|
68
|
+
CcAddresses: ccAddresses,
|
|
69
|
+
BccAddresses: bccAddresses
|
|
70
|
+
},
|
|
71
|
+
Message,
|
|
72
|
+
ReplyToAddresses: replyToAddresses
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const command = new SendEmailCommand(params);
|
|
76
|
+
const res = await sesClient.send(command);
|
|
77
|
+
if (!res.MessageId) {
|
|
78
|
+
throw new Error('sendEmail: SES response did not contain a MessageId');
|
|
79
|
+
}
|
|
80
|
+
return res.MessageId;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
// Normalize error for callers
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
let code;
|
|
86
|
+
if (err && typeof err === 'object') {
|
|
87
|
+
const e = err;
|
|
88
|
+
if (typeof e['name'] === 'string')
|
|
89
|
+
code = e['name'];
|
|
90
|
+
}
|
|
91
|
+
const details = { message, code };
|
|
92
|
+
// Re-throw a clear error for the caller to handle
|
|
93
|
+
throw new Error(`sendEmail: failed to send email: ${JSON.stringify(details)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.7.
|
|
6
|
+
"version": "0.7.2",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/Webamoki/Web-svelte"
|
|
11
|
+
},
|
|
8
12
|
"files": [
|
|
9
13
|
"dist",
|
|
10
14
|
"!dist/**/*.test.*",
|
|
@@ -43,6 +47,10 @@
|
|
|
43
47
|
"types": "./dist/utils/form/index.d.ts",
|
|
44
48
|
"import": "./dist/utils/form/index.js"
|
|
45
49
|
},
|
|
50
|
+
"./utils/email": {
|
|
51
|
+
"types": "./dist/utils/email/index.d.ts",
|
|
52
|
+
"import": "./dist/utils/email/index.js"
|
|
53
|
+
},
|
|
46
54
|
"./server/form-handler": {
|
|
47
55
|
"types": "./dist/server/form-handler.d.ts",
|
|
48
56
|
"import": "./dist/server/form-handler.js"
|
|
@@ -102,6 +110,7 @@
|
|
|
102
110
|
"svelte"
|
|
103
111
|
],
|
|
104
112
|
"dependencies": {
|
|
113
|
+
"@aws-sdk/client-ses": "^3.948.0",
|
|
105
114
|
"@internationalized/date": "^3.10.0",
|
|
106
115
|
"@lucide/svelte": "^0.553.0",
|
|
107
116
|
"@sveltejs/adapter-auto": "^7.0.0",
|