irismail 0.1.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -111
- package/dist/chunk-64MVNASE.mjs +42 -0
- package/dist/chunk-IQG3OBQL.mjs +127 -0
- package/dist/index.d.mts +3 -10
- package/dist/index.d.ts +3 -10
- package/dist/index.js +112 -351
- package/dist/index.mjs +8 -18
- package/dist/react/index.d.mts +72 -51
- package/dist/react/index.d.ts +72 -51
- package/dist/react/index.js +85 -180
- package/dist/react/index.mjs +3 -3
- package/dist/server/index.d.mts +38 -98
- package/dist/server/index.d.ts +38 -98
- package/dist/server/index.js +25 -167
- package/dist/server/index.mjs +3 -11
- package/package.json +15 -6
- package/dist/chunk-FTNSOYOW.mjs +0 -223
- package/dist/chunk-XGASTZZ6.mjs +0 -180
package/README.md
CHANGED
|
@@ -1,157 +1,132 @@
|
|
|
1
1
|
# IrisMail
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A lightweight npm package for sending emails via Gmail and beautiful OTP input components for React.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- 📧 **Email Service**: Simple Gmail-based email sending with minimal configuration
|
|
8
|
+
- 🔢 **OTP Input**: Beautiful, accessible OTP input components with copy-paste support
|
|
9
|
+
- ⚡ **Easy to Use**: Minimal API surface - just a few props to get started
|
|
10
|
+
- 🛡️ **Type-Safe**: Built with TypeScript
|
|
11
|
+
- ⚙️ **Zero Config**: Just add your Gmail credentials - no SMTP configuration needed
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install irismail
|
|
17
|
-
# or
|
|
18
|
-
yarn add irismail
|
|
19
|
-
# or
|
|
20
|
-
pnpm add irismail
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
##
|
|
19
|
+
## Email Service
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Initialize the service with your email configuration and a secret key for encryption.
|
|
21
|
+
Send emails with just your Gmail credentials:
|
|
28
22
|
|
|
29
23
|
```typescript
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
transport: {
|
|
37
|
-
host: 'smtp.gmail.com',
|
|
38
|
-
port: 587,
|
|
39
|
-
auth: {
|
|
40
|
-
user: process.env.GMAIL_USER,
|
|
41
|
-
pass: process.env.GMAIL_PASS,
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
defaults: {
|
|
45
|
-
from: {
|
|
46
|
-
name: 'My App',
|
|
47
|
-
address: process.env.GMAIL_USER,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
24
|
+
import { IrisMail } from 'irismail/server';
|
|
25
|
+
|
|
26
|
+
const mail = new IrisMail({
|
|
27
|
+
auth: {
|
|
28
|
+
user: process.env.GMAIL_USER!,
|
|
29
|
+
pass: process.env.GMAIL_APP_PASSWORD!,
|
|
50
30
|
},
|
|
51
|
-
secretKey: process.env.OTP_SECRET_KEY, // Must be 32 chars
|
|
52
31
|
});
|
|
53
32
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// Send email
|
|
62
|
-
await irismail.email.sendEmail({
|
|
63
|
-
to: email,
|
|
64
|
-
subject: 'Your Verification Code',
|
|
65
|
-
html: `<p>Your code is: <b>${otp}</b></p>`,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return NextResponse.json({ encryptedOtp });
|
|
69
|
-
}
|
|
33
|
+
await mail.sendMail({
|
|
34
|
+
from: process.env.GMAIL_USER!,
|
|
35
|
+
to: 'user@example.com',
|
|
36
|
+
subject: 'Hello!',
|
|
37
|
+
html: '<h1>Welcome</h1><p>Thanks for signing up!</p>',
|
|
38
|
+
});
|
|
70
39
|
```
|
|
71
40
|
|
|
72
|
-
|
|
41
|
+
> **Note:** You'll need to generate a [Gmail App Password](https://support.google.com/accounts/answer/185833) for authentication.
|
|
73
42
|
|
|
74
|
-
|
|
43
|
+
## OTP Input Component
|
|
44
|
+
|
|
45
|
+
### Basic Usage
|
|
75
46
|
|
|
76
47
|
```tsx
|
|
77
|
-
// components/verify-form.tsx
|
|
78
48
|
'use client';
|
|
79
49
|
|
|
80
50
|
import { useState } from 'react';
|
|
81
|
-
import {
|
|
51
|
+
import { OTP } from 'irismail/react';
|
|
82
52
|
|
|
83
|
-
export function VerifyForm(
|
|
84
|
-
const [
|
|
85
|
-
const { otpTimeLeft, resendTimeLeft, formatTime } = useOTP({ email });
|
|
53
|
+
export function VerifyForm() {
|
|
54
|
+
const [code, setCode] = useState('');
|
|
86
55
|
|
|
87
56
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
>
|
|
94
|
-
<InputOTPGroup>
|
|
95
|
-
<InputOTPSlot index={0} />
|
|
96
|
-
<InputOTPSlot index={1} />
|
|
97
|
-
<InputOTPSlot index={2} />
|
|
98
|
-
<InputOTPSlot index={3} />
|
|
99
|
-
<InputOTPSlot index={4} />
|
|
100
|
-
<InputOTPSlot index={5} />
|
|
101
|
-
</InputOTPGroup>
|
|
102
|
-
</InputOTP>
|
|
103
|
-
|
|
104
|
-
<div className="text-sm">
|
|
105
|
-
Time remaining: {formatTime(otpTimeLeft)}
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
57
|
+
<OTP
|
|
58
|
+
value={code}
|
|
59
|
+
onChange={setCode}
|
|
60
|
+
onComplete={(value) => verify(value)}
|
|
61
|
+
/>
|
|
108
62
|
);
|
|
109
63
|
}
|
|
110
64
|
```
|
|
111
65
|
|
|
112
|
-
|
|
66
|
+
### With Error State
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<OTP
|
|
70
|
+
value={code}
|
|
71
|
+
onChange={setCode}
|
|
72
|
+
error={isInvalid}
|
|
73
|
+
/>
|
|
74
|
+
```
|
|
113
75
|
|
|
114
|
-
|
|
76
|
+
### Custom Length
|
|
115
77
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
78
|
+
```tsx
|
|
79
|
+
<OTP length={4} value={code} onChange={setCode} />
|
|
80
|
+
```
|
|
120
81
|
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
cp .env.example .env.local
|
|
124
|
-
# Edit .env.local with your Gmail credentials and secret key
|
|
125
|
-
```
|
|
82
|
+
### Composition Pattern
|
|
126
83
|
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
npm run dev
|
|
130
|
-
```
|
|
84
|
+
For custom layouts with separators:
|
|
131
85
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
86
|
+
```tsx
|
|
87
|
+
import {
|
|
88
|
+
InputOTP,
|
|
89
|
+
InputOTPGroup,
|
|
90
|
+
InputOTPSlot,
|
|
91
|
+
InputOTPSeparator
|
|
92
|
+
} from 'irismail/react';
|
|
93
|
+
|
|
94
|
+
<InputOTP maxLength={6} value={code} onChange={setCode}>
|
|
95
|
+
<InputOTPGroup>
|
|
96
|
+
<InputOTPSlot index={0} />
|
|
97
|
+
<InputOTPSlot index={1} />
|
|
98
|
+
<InputOTPSlot index={2} />
|
|
99
|
+
</InputOTPGroup>
|
|
100
|
+
<InputOTPSeparator />
|
|
101
|
+
<InputOTPGroup>
|
|
102
|
+
<InputOTPSlot index={3} />
|
|
103
|
+
<InputOTPSlot index={4} />
|
|
104
|
+
<InputOTPSlot index={5} />
|
|
105
|
+
</InputOTPGroup>
|
|
106
|
+
</InputOTP>
|
|
107
|
+
```
|
|
136
108
|
|
|
137
|
-
|
|
109
|
+
## OTP Props
|
|
138
110
|
|
|
139
|
-
|
|
111
|
+
| Prop | Type | Default | Description |
|
|
112
|
+
|------|------|---------|-------------|
|
|
113
|
+
| `length` | number | 6 | Number of OTP digits |
|
|
114
|
+
| `value` | string | — | Controlled input value |
|
|
115
|
+
| `onChange` | (value: string) => void | — | Called when value changes |
|
|
116
|
+
| `onComplete` | (value: string) => void | — | Called when all digits entered |
|
|
117
|
+
| `disabled` | boolean | false | Disable the input |
|
|
118
|
+
| `error` | boolean | false | Show error styling |
|
|
119
|
+
| `autoFocus` | boolean | false | Auto focus first slot |
|
|
120
|
+
| `name` | string | — | Name attribute for forms |
|
|
121
|
+
| `className` | string | — | Container className |
|
|
140
122
|
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
npm login
|
|
144
|
-
```
|
|
123
|
+
## Documentation
|
|
145
124
|
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
npm run build
|
|
149
|
-
```
|
|
125
|
+
For full documentation and interactive examples, run the docs site:
|
|
150
126
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```
|
|
127
|
+
```bash
|
|
128
|
+
npm run site
|
|
129
|
+
```
|
|
155
130
|
|
|
156
131
|
## License
|
|
157
132
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__publicField
|
|
3
|
+
} from "./chunk-QZ7TP4HQ.mjs";
|
|
4
|
+
|
|
5
|
+
// src/server/email.ts
|
|
6
|
+
import nodemailer from "nodemailer";
|
|
7
|
+
var IrisMail = class {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
__publicField(this, "transporter");
|
|
10
|
+
this.transporter = nodemailer.createTransport({
|
|
11
|
+
host: "smtp.gmail.com",
|
|
12
|
+
port: 587,
|
|
13
|
+
secure: false,
|
|
14
|
+
// true for 465, false for other ports
|
|
15
|
+
auth: config.auth
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Send an email
|
|
20
|
+
* @param options - Email options (from, to, subject, html)
|
|
21
|
+
* @returns Promise with success status and messageId
|
|
22
|
+
*/
|
|
23
|
+
async sendMail(options) {
|
|
24
|
+
const { from, to, subject, html } = options;
|
|
25
|
+
const text = html.replace(/<[^>]*>/g, "");
|
|
26
|
+
const info = await this.transporter.sendMail({
|
|
27
|
+
from,
|
|
28
|
+
to,
|
|
29
|
+
subject,
|
|
30
|
+
html,
|
|
31
|
+
text
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
messageId: info.messageId
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
IrisMail
|
|
42
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/react/components/input-otp.tsx
|
|
2
|
+
import { OTPInput, OTPInputContext } from "input-otp";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
// src/utils/constants.ts
|
|
6
|
+
import { clsx } from "clsx";
|
|
7
|
+
import { twMerge } from "tailwind-merge";
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/react/components/input-otp.tsx
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var InputOTPStyleContext = React.createContext({});
|
|
15
|
+
var InputOTP = React.forwardRef(({ className, containerClassName, error, ...props }, ref) => /* @__PURE__ */ jsx(InputOTPStyleContext.Provider, { value: { error }, children: /* @__PURE__ */ jsx(
|
|
16
|
+
OTPInput,
|
|
17
|
+
{
|
|
18
|
+
ref,
|
|
19
|
+
containerClassName: cn(
|
|
20
|
+
"flex items-center gap-2 has-[:disabled]:opacity-50",
|
|
21
|
+
containerClassName
|
|
22
|
+
),
|
|
23
|
+
className: cn("disabled:cursor-not-allowed", className),
|
|
24
|
+
...props
|
|
25
|
+
}
|
|
26
|
+
) }));
|
|
27
|
+
InputOTP.displayName = "InputOTP";
|
|
28
|
+
var InputOTPGroup = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
ref,
|
|
32
|
+
className: cn("flex items-center gap-2", className),
|
|
33
|
+
...props
|
|
34
|
+
}
|
|
35
|
+
));
|
|
36
|
+
InputOTPGroup.displayName = "InputOTPGroup";
|
|
37
|
+
var InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
|
|
38
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
39
|
+
const { error } = React.useContext(InputOTPStyleContext);
|
|
40
|
+
const slot = inputOTPContext.slots[index];
|
|
41
|
+
const { char, hasFakeCaret, isActive } = slot;
|
|
42
|
+
return /* @__PURE__ */ jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
ref,
|
|
46
|
+
className: cn(
|
|
47
|
+
// Base styles
|
|
48
|
+
"relative flex h-12 w-10 items-center justify-center",
|
|
49
|
+
"border-2 rounded-lg",
|
|
50
|
+
"font-mono text-lg font-medium",
|
|
51
|
+
"transition-all duration-150",
|
|
52
|
+
// Default state
|
|
53
|
+
"border-zinc-700 bg-zinc-900 text-white",
|
|
54
|
+
// Filled state
|
|
55
|
+
char && "border-zinc-500",
|
|
56
|
+
// Active/focus state
|
|
57
|
+
isActive && "ring-2 ring-offset-2 ring-offset-zinc-950 ring-indigo-500 border-indigo-500",
|
|
58
|
+
// Error state
|
|
59
|
+
error && "border-red-500/70 bg-red-500/10",
|
|
60
|
+
error && isActive && "ring-red-500",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
"data-active": isActive,
|
|
64
|
+
"data-filled": Boolean(char),
|
|
65
|
+
...props,
|
|
66
|
+
children: [
|
|
67
|
+
char,
|
|
68
|
+
hasFakeCaret && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-0.5 animate-caret-blink rounded-full bg-indigo-400" }) })
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
InputOTPSlot.displayName = "InputOTPSlot";
|
|
74
|
+
var InputOTPSeparator = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
ref,
|
|
78
|
+
role: "separator",
|
|
79
|
+
className: cn("flex items-center justify-center text-zinc-500", className),
|
|
80
|
+
...props,
|
|
81
|
+
children: /* @__PURE__ */ jsx("span", { className: "w-3 h-0.5 bg-zinc-600 rounded-full" })
|
|
82
|
+
}
|
|
83
|
+
));
|
|
84
|
+
InputOTPSeparator.displayName = "InputOTPSeparator";
|
|
85
|
+
var OTP = React.forwardRef(
|
|
86
|
+
({
|
|
87
|
+
length = 6,
|
|
88
|
+
value,
|
|
89
|
+
onChange,
|
|
90
|
+
onComplete,
|
|
91
|
+
disabled,
|
|
92
|
+
error,
|
|
93
|
+
autoFocus,
|
|
94
|
+
name,
|
|
95
|
+
className,
|
|
96
|
+
pattern = "^[0-9]*$"
|
|
97
|
+
}, ref) => {
|
|
98
|
+
const slotIndices = Array.from({ length }, (_, i) => i);
|
|
99
|
+
return /* @__PURE__ */ jsx(
|
|
100
|
+
InputOTP,
|
|
101
|
+
{
|
|
102
|
+
ref,
|
|
103
|
+
maxLength: length,
|
|
104
|
+
value,
|
|
105
|
+
onChange,
|
|
106
|
+
onComplete,
|
|
107
|
+
disabled,
|
|
108
|
+
error,
|
|
109
|
+
autoFocus,
|
|
110
|
+
name,
|
|
111
|
+
pattern,
|
|
112
|
+
containerClassName: className,
|
|
113
|
+
children: /* @__PURE__ */ jsx(InputOTPGroup, { children: slotIndices.map((index) => /* @__PURE__ */ jsx(InputOTPSlot, { index }, index)) })
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
OTP.displayName = "OTP";
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
cn,
|
|
122
|
+
InputOTP,
|
|
123
|
+
InputOTPGroup,
|
|
124
|
+
InputOTPSlot,
|
|
125
|
+
InputOTPSeparator,
|
|
126
|
+
OTP
|
|
127
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,
|
|
1
|
+
export { IrisMail, IrisMailConfig, SendMailOptions, SendMailResult } from './server/index.mjs';
|
|
2
|
+
export { InputOTP, InputOTPGroup, InputOTPProps, InputOTPSeparator, InputOTPSlot, OTP, OTPProps } from './react/index.mjs';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import 'input-otp';
|
|
5
5
|
import 'react';
|
|
6
6
|
|
|
7
7
|
declare function cn(...inputs: ClassValue[]): string;
|
|
8
|
-
declare const OTP_DEFAULTS: {
|
|
9
|
-
LENGTH: number;
|
|
10
|
-
EXPIRY_MINUTES: number;
|
|
11
|
-
RESEND_COOLDOWN_SECONDS: number;
|
|
12
|
-
MAX_ATTEMPTS: number;
|
|
13
|
-
RATE_LIMIT_RESET_HOURS: number;
|
|
14
|
-
};
|
|
15
8
|
|
|
16
|
-
export {
|
|
9
|
+
export { cn };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot,
|
|
1
|
+
export { IrisMail, IrisMailConfig, SendMailOptions, SendMailResult } from './server/index.js';
|
|
2
|
+
export { InputOTP, InputOTPGroup, InputOTPProps, InputOTPSeparator, InputOTPSlot, OTP, OTPProps } from './react/index.js';
|
|
3
3
|
import { ClassValue } from 'clsx';
|
|
4
4
|
import 'input-otp';
|
|
5
5
|
import 'react';
|
|
6
6
|
|
|
7
7
|
declare function cn(...inputs: ClassValue[]): string;
|
|
8
|
-
declare const OTP_DEFAULTS: {
|
|
9
|
-
LENGTH: number;
|
|
10
|
-
EXPIRY_MINUTES: number;
|
|
11
|
-
RESEND_COOLDOWN_SECONDS: number;
|
|
12
|
-
MAX_ATTEMPTS: number;
|
|
13
|
-
RATE_LIMIT_RESET_HOURS: number;
|
|
14
|
-
};
|
|
15
8
|
|
|
16
|
-
export {
|
|
9
|
+
export { cn };
|