stashing code

This commit is contained in:
user
2025-10-20 17:07:41 +03:00
commit f5b99afc8f
890 changed files with 54823 additions and 0 deletions

34
packages/email/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

15
packages/email/README.md Normal file
View File

@@ -0,0 +1,15 @@
# email
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.8. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

View File

@@ -0,0 +1,273 @@
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Preview,
Section,
Text,
Row,
Column,
Img,
Font,
} from "@react-email/components";
import { Tailwind } from "@react-email/tailwind";
interface PnrConfirmationProps {
pnr: string;
origin: string;
destination: string;
departureDate: string;
passengerName: string;
returnDate?: string;
baseUrl?: string;
logoPath?: string;
companyName: string;
}
export function PnrConfirmationEmail({
pnr,
origin,
destination,
passengerName,
departureDate,
returnDate,
baseUrl,
logoPath,
companyName,
}: PnrConfirmationProps) {
const previewText = `Flight Confirmation: ${origin} to ${destination} - PNR: ${pnr}`;
// Format dates for better display
const formattedDepartureDate = new Date(departureDate).toLocaleDateString(
"en-US",
{
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
},
);
const formattedReturnDate = returnDate
? new Date(returnDate).toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
})
: null;
return (
<Html>
<Head>
<Font
fontFamily="Poppins"
fallbackFontFamily={["Verdana", "Arial", "sans-serif"]}
webFont={{
url: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap",
format: "woff2",
}}
fontWeight={400}
fontStyle="normal"
/>
<Font
fontFamily="Poppins"
fallbackFontFamily={["Verdana", "Arial", "sans-serif"]}
webFont={{
url: "https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap",
format: "woff2",
}}
fontWeight={600}
fontStyle="normal"
/>
</Head>
<Preview>{previewText}</Preview>
<Tailwind
config={{
theme: {
extend: {
colors: {
brand: {
50: "#fcf3f7",
100: "#fbe8f1",
200: "#f8d2e5",
300: "#f4adce",
400: "#ec7aad",
500: "#e2528d",
600: "#d5467a",
700: "#b42253",
800: "#951f45",
900: "#7d1e3c",
950: "#4c0b20",
},
primary: "#e2528d",
accent: "#f4adce",
light: "#fcf3f7",
dark: "#4c0b20",
},
fontFamily: {
sans: ["Poppins", "Helvetica", "Arial", "sans-serif"],
},
},
},
}}
>
<Body className="bg-light font-sans">
<Container className="mx-auto pt-8 pb-8 mb-0">
<Section className="bg-white rounded-lg p-8 shadow-lg mb-8">
{/* Header with Logo */}
<Row>
<Column className="text-center">
{logoPath && (
<Img
src={logoPath}
alt={`${companyName} logo`}
width="120"
height="auto"
className="mx-auto mb-4"
/>
)}
<Text className="text-center text-brand-400 text-sm mb-6">
Your journey begins here
</Text>
</Column>
</Row>
<Hr className="border-t border-brand-100 my-6" />
{/* Booking Confirmation */}
<Heading className="text-brand-900 text-xl font-bold mb-4">
Booking Confirmation
</Heading>
<Text className="text-brand-800 mb-4">Hey {passengerName},</Text>
<Text className="text-brand-800 mb-4">
Your flight has been successfully booked! Below are the details
of your trip.
</Text>
{/* PNR Highlight Box */}
<Section className="bg-brand-50 border-l-4 border-primary p-4 mb-6">
<Text className="font-bold text-brand-900 mb-1">
Booking Reference (PNR):
</Text>
<Text className="text-2xl font-semibold text-primary mb-0">
{pnr}
</Text>
</Section>
{/* Flight Details - Outbound */}
<Section className="bg-white border border-brand-100 rounded-lg p-4 mb-6">
<Heading className="text-lg font-bold text-primary mb-4">
Outbound Flight
</Heading>
<Row>
<Column className="pr-4">
<Text className="font-bold text-brand-900 mb-1">Date:</Text>
<Text className="text-brand-800 mb-3">
{formattedDepartureDate}
</Text>
</Column>
<Column>
<Text className="font-bold text-brand-900 mb-1">
Route:
</Text>
<Text className="text-brand-800 mb-3">
{origin} {destination}
</Text>
</Column>
</Row>
</Section>
{/* Flight Details - Return (conditional) */}
{returnDate && (
<Section className="bg-white border border-brand-100 rounded-lg p-4 mb-6">
<Heading className="text-lg font-bold text-primary mb-4">
Return Flight
</Heading>
<Row>
<Column className="pr-4">
<Text className="font-bold text-brand-900 mb-1">
Date:
</Text>
<Text className="text-brand-800 mb-3">
{formattedReturnDate}
</Text>
</Column>
<Column>
<Text className="font-bold text-brand-900 mb-1">
Route:
</Text>
<Text className="text-brand-800 mb-3">
{destination} {origin}
</Text>
</Column>
</Row>
</Section>
)}
<Text className="text-brand-700 mb-4 text-center text-sm">
Please note that it may take up to 48 hours for the payment to
be processed and your flight to be confirmed with the airline.
</Text>
{/* Call to action */}
<Section className="text-center mt-8 mb-6">
<Button
className="bg-primary text-white font-bold px-6 py-3 rounded-md"
href={`${baseUrl}/track?pnr=${pnr}`}
>
View Booking Online
</Button>
</Section>
<Text className="text-brand-700 text-xs mb-4 text-center">
You can use the button above or your booking reference to check
in online, make changes to your booking, or add additional
services.
</Text>
<Hr className="border-t border-brand-100 my-6" />
{/* Footer */}
<Text className="text-brand-500 text-sm text-center">
Thank you for choosing {companyName} for your journey. We hope
you have a wonderful trip!
</Text>
</Section>
{/* Footer disclaimer */}
<Text className="text-brand-400 text-xs text-center px-4">
This is an automated message. Please do not reply to this email.
For any changes to your booking, please use the "Manage My
Booking" button above or contact our customer service team.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
PnrConfirmationEmail.PreviewProps = {
pnr: "ABC123",
origin: "SFO",
destination: "JFK",
departureDate: "2023-12-15",
returnDate: "2023-12-22",
passengerName: "John Smith",
baseUrl: "https://flytickettravel.com",
logoPath: "https://flytickettravel.com/assets/logos/logo-main.svg",
companyName: "FlyTicketTravel",
};
export default PnrConfirmationEmail;

89
packages/email/index.ts Normal file
View File

@@ -0,0 +1,89 @@
import {
EmailProviders,
type EmailProviderTypes,
type EmailServiceProvider,
type EmailPayload,
} from "./src/data";
import { ERROR_CODES, type Result } from "@pkg/result";
import { getError } from "@pkg/logger";
import { ResendEmailSvcProvider } from "./src/resend.provider";
export class EmailService {
private provider: EmailServiceProvider | null = null;
initialize(apiKey: string): Result<boolean> {
try {
this.provider = this.createProvider(EmailProviders.Resend, { apiKey });
return this.provider.initialize();
} catch (e) {
return {
error: getError(
{
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to initialize email provider",
userHint: "Please check email configuration",
detail: "Error creating email provider",
},
e,
),
};
}
}
private createProvider(
type: EmailProviderTypes,
config: { apiKey: string },
): EmailServiceProvider {
switch (type) {
case EmailProviders.Resend:
return new ResendEmailSvcProvider(config.apiKey);
default:
throw new Error(`Unsupported email provider: ${type}`);
}
}
async sendEmail(
payload: EmailPayload,
config: { apiKey: string },
): Promise<Result<boolean>> {
if (!this.provider) {
const initResult = this.initialize(config.apiKey);
if (initResult.error) return initResult;
}
return await this.provider!.sendEmail(payload);
}
async sendEmailWithReactTemplate(
payload: {
to: string;
subject: string;
from: string;
cc?: string[];
bcc?: string[];
attachments?: any[];
template: string;
templateData: any;
},
config: { apiKey: string },
): Promise<Result<boolean>> {
if (!this.provider) {
const initResult = this.initialize(config.apiKey);
if (initResult.error) return initResult;
}
// This assumes you're using Resend as the provider
if (this.provider instanceof ResendEmailSvcProvider) {
return await this.provider.sendEmailWithReact(payload);
} else {
return {
error: getError({
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message:
"React templates are only supported with the Resend provider",
userHint: "Please configure Resend as your email provider",
detail: "Current email provider doesn't support React templates",
}),
};
}
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "@pkg/email",
"module": "index.ts",
"type": "module",
"scripts": {
"sample-dev": "email dev --port 5069"
},
"devDependencies": {
"@types/bun": "latest",
"react-email": "4.0.11"
},
"peerDependencies": {
"typescript": "^5"
},
"private": true,
"dependencies": {
"@pkg/logger": "workspace:*",
"@pkg/result": "workspace:*",
"@react-email/components": "0.0.38",
"@react-email/font": "0.0.9",
"@react-email/tailwind": "1.0.5",
"react": "19.1.0",
"react-dom": "19.1.0",
"resend": "^4.5.0",
"zod": "^3.24.3"
}
}

View File

@@ -0,0 +1,44 @@
import type { Result } from "@pkg/result";
import { z } from "zod";
export interface EmailServiceProvider {
initialize(): Result<boolean>;
sendEmail(payload: EmailPayload): Promise<Result<boolean>>;
sendEmailWithReact(payload: {
to: string;
subject: string;
from: string;
cc?: string[];
bcc?: string[];
attachments?: any[];
template: string;
templateData: any;
}): Promise<Result<boolean>>;
}
export const EmailProviders = {
Resend: "Resend",
SendGrid: "SendGrid",
} as const;
export type EmailProviderTypes = keyof typeof EmailProviders;
export const emailConfigModel = z.object({
provider: z.string(),
config: z.object({
apiKey: z.string(),
}),
});
export type EmailConfig = z.infer<typeof emailConfigModel>;
export const emailPayloadModel = z.object({
to: z.string().email(),
subject: z.string(),
from: z.string().email().optional(),
cc: z.array(z.string().email()).optional(),
bcc: z.array(z.string().email()).optional(),
attachments: z.array(z.any()).optional(),
html: z.string().optional(),
text: z.string().optional(),
react: z.any(),
});
export type EmailPayload = z.infer<typeof emailPayloadModel>;

View File

@@ -0,0 +1,127 @@
import { Resend } from "resend";
import { type EmailServiceProvider, type EmailPayload } from "./data";
import { ERROR_CODES, type Result } from "@pkg/result";
import { getError } from "@pkg/logger";
import PnrConfirmationEmail from "../emails/pnr-confirmation";
import { createElement } from "react";
export class ResendEmailSvcProvider implements EmailServiceProvider {
private client: Resend | null = null;
constructor(private apiKey: string) {}
initialize(): Result<boolean> {
try {
this.client = new Resend(this.apiKey);
return { data: true };
} catch (e) {
return {
error: getError(
{
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to initialize Resend client",
userHint: "Please check API key configuration",
detail: "Error initializing Resend client",
},
e,
),
};
}
}
async sendEmail(payload: EmailPayload): Promise<Result<boolean>> {
try {
if (!this.client) {
const initResult = this.initialize();
if (initResult.error) return initResult;
}
await this.client!.emails.send({
from: payload.from || "onboarding@resend.dev",
to: payload.to,
subject: payload.subject,
html: payload.html ?? "",
text: payload.text,
cc: payload.cc,
bcc: payload.bcc,
attachments: payload.attachments,
});
return { data: true };
} catch (e) {
return {
error: getError(
{
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to send email",
userHint: "Please try again later",
detail: "Error sending email via Resend",
},
e,
),
};
}
}
async sendEmailWithReact(payload: {
to: string;
subject: string;
from: string;
cc?: string[];
bcc?: string[];
attachments?: any[];
template: string;
templateData: any;
}): Promise<Result<boolean>> {
try {
if (!this.client) {
const initResult = this.initialize();
if (initResult.error) return initResult;
}
let body = undefined as any;
switch (payload.template) {
case "pnr-confirmation":
body = createElement(PnrConfirmationEmail, payload.templateData);
break;
default:
return {
error: getError(
{
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Unsupported email template",
userHint: "Please check email configuration",
detail: "Unsupported email template",
},
new Error("Unsupported email template"),
),
};
}
await this.client!.emails.send({
from: payload.from,
to: payload.to,
subject: payload.subject,
cc: payload.cc,
bcc: payload.bcc,
attachments: payload.attachments,
react: body,
});
return { data: true };
} catch (e) {
return {
error: getError(
{
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
message: "Failed to send email with React template",
userHint: "Please try again later",
detail: "Error sending email via Resend",
},
e,
),
};
}
}
}

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}