Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ac090aff3 | |||
| 696f9d361d | |||
| 31840da9e7 | |||
| 96ec2c7d8d | |||
| 9029375247 |
5
.changeset/init-mail-package.md
Normal file
5
.changeset/init-mail-package.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@mintel/mail": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Initial release of the branded email system package.
|
||||||
@@ -4,10 +4,30 @@ The Mintel CLI is the primary automation tool for managing the monorepo and ensu
|
|||||||
|
|
||||||
## 🚀 Installation
|
## 🚀 Installation
|
||||||
|
|
||||||
The CLI is intended to be used within the monorepo:
|
### Using npx (Recommended)
|
||||||
|
|
||||||
|
Run the CLI without installing it globally. This always uses the latest version from the registry:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install
|
npx @mintel/cli init apps/my-new-website.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Global Installation
|
||||||
|
|
||||||
|
Install the CLI globally from the Mintel registry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @mintel/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development (Local Link)
|
||||||
|
|
||||||
|
If you are contributing to the CLI, you can link it locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packages/cli
|
||||||
|
pnpm build
|
||||||
|
npm link
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠 Commands
|
## 🛠 Commands
|
||||||
@@ -17,10 +37,11 @@ pnpm install
|
|||||||
Scaffolds a new, production-ready client website in the specified path.
|
Scaffolds a new, production-ready client website in the specified path.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm --filter @mintel/cli start init apps/my-new-website.com
|
mintel init apps/my-new-website.com
|
||||||
```
|
```
|
||||||
|
|
||||||
#### What it does:
|
#### What it does:
|
||||||
|
|
||||||
1. **Project Structure**: Creates a modern Next.js directory layout.
|
1. **Project Structure**: Creates a modern Next.js directory layout.
|
||||||
2. **Shared Configs**: Generates `package.json`, `tsconfig.json`, and `eslint.config.mjs` that extend the `@mintel` shared packages.
|
2. **Shared Configs**: Generates `package.json`, `tsconfig.json`, and `eslint.config.mjs` that extend the `@mintel` shared packages.
|
||||||
3. **Localization**: Sets up a localized routing structure (`src/app/[locale]`) with `next-intl` pre-configured.
|
3. **Localization**: Sets up a localized routing structure (`src/app/[locale]`) with `next-intl` pre-configured.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@@ -87,7 +88,7 @@ program
|
|||||||
.action(async (projectPath) => {
|
.action(async (projectPath) => {
|
||||||
const fullPath = path.isAbsolute(projectPath)
|
const fullPath = path.isAbsolute(projectPath)
|
||||||
? projectPath
|
? projectPath
|
||||||
: path.resolve(process.cwd(), "../../", projectPath);
|
: path.resolve(process.cwd(), projectPath);
|
||||||
const projectName = path.basename(fullPath);
|
const projectName = path.basename(fullPath);
|
||||||
|
|
||||||
console.log(chalk.blue(`Initializing new project: ${projectName}...`));
|
console.log(chalk.blue(`Initializing new project: ${projectName}...`));
|
||||||
|
|||||||
15
packages/gatekeeper/dev-output.log
Normal file
15
packages/gatekeeper/dev-output.log
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
> @mintel/gatekeeper@1.0.0 dev
|
||||||
|
> next dev
|
||||||
|
|
||||||
|
⚠ Port 3000 is in use, trying 3001 instead.
|
||||||
|
▲ Next.js 15.1.6
|
||||||
|
- Local: http://localhost:3001
|
||||||
|
- Network: http://192.168.1.126:3001
|
||||||
|
- Experiments (use with caution):
|
||||||
|
· clientTraceMetadata
|
||||||
|
|
||||||
|
✓ Starting...
|
||||||
|
warn - It seems like you don't have a global error handler set up. It is recommended that you add a global-error.js file with Sentry instrumentation so that React rendering errors are reported to Sentry. Read more: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#react-render-errors-in-app-router (you can suppress this warning by setting SENTRY_SUPPRESS_GLOBAL_ERROR_HANDLER_FILE_WARNING=1 as environment variable)
|
||||||
|
✓ Ready in 2.7s
|
||||||
|
[?25h
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@mintel/eslint-config": "workspace:*",
|
"@mintel/eslint-config": "workspace:*",
|
||||||
"@mintel/next-config": "workspace:*",
|
"@mintel/next-config": "workspace:*",
|
||||||
"@mintel/tsconfig": "workspace:*",
|
"@mintel/tsconfig": "workspace:*",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"@types/react-dom": "^19.0.0",
|
"@types/react-dom": "^19.0.0",
|
||||||
|
|||||||
6
packages/gatekeeper/postcss.config.cjs
Normal file
6
packages/gatekeeper/postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
12
packages/gatekeeper/public/icon-white.svg
Normal file
12
packages/gatekeeper/public/icon-white.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 29 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Lock, ShieldCheck, ArrowRight } from "lucide-react";
|
import { ArrowRight, ShieldCheck } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
interface LoginPageProps {
|
interface LoginPageProps {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
@@ -12,7 +13,6 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
|||||||
const error = params.error === "1";
|
const error = params.error === "1";
|
||||||
|
|
||||||
const projectName = process.env.PROJECT_NAME || "Mintel";
|
const projectName = process.env.PROJECT_NAME || "Mintel";
|
||||||
const projectColor = process.env.PROJECT_COLOR || "#82ed20";
|
|
||||||
|
|
||||||
async function login(formData: FormData) {
|
async function login(formData: FormData) {
|
||||||
"use server";
|
"use server";
|
||||||
@@ -41,92 +41,93 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center relative overflow-hidden bg-mintel-dark">
|
<div className="min-h-screen flex items-center justify-center relative bg-white font-serif antialiased overflow-hidden">
|
||||||
{/* Background Decor */}
|
{/* Background Decor - Signature mintel.me style */}
|
||||||
<div className="absolute inset-0 bg-grid pointer-events-none opacity-20" />
|
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 pointer-events-none opacity-30"
|
className="absolute inset-0 pointer-events-none opacity-[0.03] scale-[1.01]"
|
||||||
style={{
|
style={{
|
||||||
background: `radial-gradient(circle at center, ${projectColor}11 0%, transparent 70%)`,
|
backgroundImage: `linear-gradient(to right, #000 1px, transparent 1px), linear-gradient(to bottom, #000 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "clamp(30px, 8vw, 40px) clamp(30px, 8vw, 40px)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative z-10 w-full max-w-md px-6 animate-in fade-in zoom-in duration-700">
|
<main className="relative z-10 w-full max-w-sm px-8 sm:px-6">
|
||||||
{/* Logo / Icon */}
|
<div className="space-y-12 sm:space-y-16 animate-fade-in">
|
||||||
<div className="flex justify-center mb-12">
|
{/* Top Icon Box - Signature mintel.me Black Square */}
|
||||||
<div
|
<div className="flex justify-center">
|
||||||
className="w-48 h-24 rounded-3xl flex items-center justify-center border border-white/10 bg-white/5 backdrop-blur-xl shadow-2xl p-6"
|
<div className="w-16 h-16 bg-black rounded-xl flex items-center justify-center shadow-xl shadow-slate-100 hover:scale-105 transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] rotate-2 hover:rotate-0">
|
||||||
style={{ borderBottom: `2px solid ${projectColor}44` }}
|
<Image
|
||||||
>
|
src="/icon-white.svg"
|
||||||
<img
|
alt="Mintel"
|
||||||
src="/logo-white.svg"
|
width={32}
|
||||||
alt={projectName}
|
height={32}
|
||||||
className="w-full h-auto opacity-90"
|
className="w-8 h-8"
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-white/[0.03] backdrop-blur-3xl border border-white/10 p-10 rounded-[48px] shadow-2xl relative overflow-hidden group">
|
|
||||||
{/* Subtle accent line */}
|
|
||||||
<div
|
|
||||||
className="absolute top-0 left-0 w-full h-1 opacity-50"
|
|
||||||
style={{
|
|
||||||
background: `linear-gradient(to right, transparent, ${projectColor}, transparent)`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mb-10 text-center">
|
|
||||||
<h1 className="text-3xl font-black mb-3 tracking-tighter uppercase italic flex items-center justify-center gap-2">
|
|
||||||
{projectName.split(" ")[0]}
|
|
||||||
<span style={{ color: projectColor }}>GATEKEEPER</span>
|
|
||||||
</h1>
|
|
||||||
<p className="text-white/40 text-sm font-medium">
|
|
||||||
Restricted Infrastructure Access
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<div className="bg-red-500/10 border border-red-500/20 text-red-200 p-4 rounded-2xl mb-8 text-sm flex items-center gap-3 animate-pulse">
|
|
||||||
<Lock className="w-5 h-5 flex-shrink-0" />
|
|
||||||
<span>Invalid access password. Please try again.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<form action={login} className="space-y-8">
|
|
||||||
<input type="hidden" name="redirect" value={redirectUrl} />
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
<label className="block text-[10px] font-black uppercase tracking-[0.3em] text-white/20 ml-5">
|
|
||||||
Authentication Code
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
required
|
|
||||||
autoFocus
|
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-3xl px-8 py-6 focus:outline-none focus:ring-2 transition-all text-xl tracking-[0.5em] text-center placeholder:tracking-normal placeholder:text-white/10"
|
|
||||||
style={{ "--tw-ring-color": `${projectColor}33` } as any}
|
|
||||||
placeholder="••••••••"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div className="space-y-12 animate-slide-up">
|
||||||
type="submit"
|
<div className="text-center space-y-4">
|
||||||
className="w-full font-black uppercase tracking-[0.2em] py-6 rounded-3xl transition-all active:scale-[0.98] flex items-center justify-center gap-3 shadow-lg hover:shadow-mintel-green/10"
|
<h1 className="text-xs font-sans font-bold uppercase tracking-[0.4em] text-slate-900 border-b border-slate-50 pb-4 inline-block mx-auto min-w-[200px]">
|
||||||
style={{ backgroundColor: projectColor, color: "#000" }}
|
{projectName} <span className="text-slate-300">Gatekeeper</span>
|
||||||
>
|
</h1>
|
||||||
Verify Identity
|
<p className="text-[10px] text-slate-400 font-sans uppercase tracking-widest italic flex items-center justify-center gap-2">
|
||||||
<ArrowRight className="w-5 h-5" />
|
<span className="w-1 h-1 bg-slate-200 rounded-full" />
|
||||||
</button>
|
Infrastructure Protection
|
||||||
</form>
|
<span className="w-1 h-1 bg-slate-200 rounded-full" />
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-12 text-center">
|
{error && (
|
||||||
<p className="text-[10px] font-bold text-white/10 uppercase tracking-[0.5em]">
|
<div className="bg-red-50 text-red-600 px-5 py-3 rounded-2xl text-[9px] font-sans font-bold uppercase tracking-widest flex items-center gap-3 border border-red-100 animate-shake">
|
||||||
© 2026 {projectName} Infrastructure
|
<ShieldCheck className="w-4 h-4" />
|
||||||
</p>
|
<span>Access Denied. Try Again.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form action={login} className="space-y-6">
|
||||||
|
<input type="hidden" name="redirect" value={redirectUrl} />
|
||||||
|
|
||||||
|
<div className="relative group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
autoComplete="current-password"
|
||||||
|
placeholder="GATEKEEPER CODE"
|
||||||
|
className="w-full bg-slate-50/50 border border-slate-200 rounded-2xl px-6 py-4 focus:outline-none focus:border-slate-900 focus:bg-white transition-all text-sm font-sans font-bold tracking-[0.3em] uppercase placeholder:text-slate-300 placeholder:tracking-widest shadow-sm shadow-slate-50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary w-full py-5 rounded-2xl text-[10px] shadow-lg shadow-slate-100"
|
||||||
|
>
|
||||||
|
Unlock Access
|
||||||
|
<ArrowRight className="ml-3 w-3 h-3 group-hover:translate-x-1 transition-transform" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Bottom Section - Full Branding Parity */}
|
||||||
|
<div className="pt-12 sm:pt-20 flex flex-col items-center gap-6 sm:gap-8">
|
||||||
|
<div className="h-px w-8 bg-slate-100" />
|
||||||
|
<div className="opacity-80 transition-opacity hover:opacity-100">
|
||||||
|
<Image
|
||||||
|
src="/logo-black.svg"
|
||||||
|
alt={projectName}
|
||||||
|
width={140}
|
||||||
|
height={40}
|
||||||
|
className="h-7 sm:h-auto grayscale contrast-125 w-auto"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[8px] font-sans font-bold text-slate-300 uppercase tracking-[0.4em] sm:tracking-[0.5em] text-center">
|
||||||
|
© 2026 MINTEL
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,83 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
@layer base {
|
||||||
--background: #000c1f;
|
html {
|
||||||
--foreground: #ffffff;
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-white text-slate-800 font-serif antialiased selection:bg-slate-900 selection:text-white;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@apply font-sans font-bold text-slate-900 tracking-tighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@apply mb-4 text-base leading-relaxed text-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@apply text-slate-900 hover:text-slate-700 transition-colors no-underline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
@layer components {
|
||||||
color: var(--foreground);
|
.narrow-container {
|
||||||
background: var(--background);
|
@apply max-w-4xl mx-auto px-6 py-10;
|
||||||
min-height: 100vh;
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@apply inline-flex items-center justify-center px-6 py-3 border border-slate-200 bg-white text-slate-600 font-sans font-bold text-sm uppercase tracking-widest rounded-full transition-all duration-500 ease-industrial hover:border-slate-400 hover:text-slate-900 hover:bg-slate-50 hover:-translate-y-0.5 hover:shadow-xl hover:shadow-slate-100 active:translate-y-0 active:shadow-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
@apply border-slate-900 text-slate-900 hover:bg-slate-900 hover:text-white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-grid {
|
/* Custom scrollbar */
|
||||||
background-image:
|
::-webkit-scrollbar {
|
||||||
linear-gradient(to right, rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
width: 8px;
|
||||||
linear-gradient(to bottom, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
height: 8px;
|
||||||
background-size: 40px 40px;
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes shake {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-shake {
|
||||||
|
animation: shake 0.2s ease-in-out 0s 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
import { Inter, Newsreader } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
|
||||||
|
const newsreader = Newsreader({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-newsreader",
|
||||||
|
style: "italic",
|
||||||
|
display: "swap",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Gatekeeper | Access Control",
|
title: "Gatekeeper | Access Control",
|
||||||
description: "Mintel Infrastructure Protection",
|
description: "Mintel Infrastructure Protection",
|
||||||
@@ -12,7 +21,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en" className={`${inter.variable} ${newsreader.variable}`}>
|
||||||
<body className="antialiased">{children}</body>
|
<body className="antialiased">{children}</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
5
packages/gatekeeper/src/app/page.tsx
Normal file
5
packages/gatekeeper/src/app/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export default function RootPage() {
|
||||||
|
redirect("/gatekeeper/login");
|
||||||
|
}
|
||||||
59
packages/gatekeeper/tailwind.config.cjs
Normal file
59
packages/gatekeeper/tailwind.config.cjs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
borderRadius: {
|
||||||
|
xl: "1rem",
|
||||||
|
"2xl": "1.5rem",
|
||||||
|
"3xl": "2rem",
|
||||||
|
full: "9999px",
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
slate: {
|
||||||
|
850: "#1e293b",
|
||||||
|
900: "#0f172a",
|
||||||
|
950: "#020617",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["var(--font-inter)", "Inter", "system-ui", "sans-serif"],
|
||||||
|
serif: ["var(--font-newsreader)", "Georgia", "serif"],
|
||||||
|
mono: ["JetBrains Mono", "monospace"],
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"fade-in": "fadeIn 0.5s ease-in-out",
|
||||||
|
"slide-up": "slideUp 0.6s ease-out",
|
||||||
|
"slide-down": "slideDown 0.6s ease-out",
|
||||||
|
shake: "shake 0.2s ease-in-out 0s 2",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
fadeIn: {
|
||||||
|
"0%": { opacity: "0" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
slideUp: {
|
||||||
|
"0%": { transform: "translateY(20px)", opacity: "0" },
|
||||||
|
"100%": { transform: "translateY(0)", opacity: "1" },
|
||||||
|
},
|
||||||
|
slideDown: {
|
||||||
|
"0%": { transform: "translateY(-20px)", opacity: "0" },
|
||||||
|
"100%": { transform: "translateY(0)", opacity: "1" },
|
||||||
|
},
|
||||||
|
shake: {
|
||||||
|
"0%, 100%": { transform: "translateX(0)" },
|
||||||
|
"25%": { transform: "translateX(-4px)" },
|
||||||
|
"75%": { transform: "translateX(4px)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
transitionTimingFunction: {
|
||||||
|
industrial: "cubic-bezier(0.23, 1, 0.32, 1)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("@tailwindcss/typography")],
|
||||||
|
};
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
content: [
|
|
||||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
mintel: {
|
|
||||||
green: "#82ed20",
|
|
||||||
blue: "#001a4d",
|
|
||||||
dark: "#000c1f",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
backgroundImage: {
|
|
||||||
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
@@ -1,20 +1,35 @@
|
|||||||
{
|
{
|
||||||
"name": "@mintel/mail",
|
"name": "@mintel/mail",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"private": true,
|
"private": false,
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://npm.infra.mintel.me"
|
||||||
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": {
|
||||||
"./templates/*": "./src/templates/*"
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./templates/*": {
|
||||||
|
"types": "./dist/templates/*.d.ts",
|
||||||
|
"import": "./dist/templates/*.js"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup src/index.ts --format esm --dts",
|
"build": "tsup src/index.ts src/templates/*.tsx --format esm --dts --clean",
|
||||||
"dev": "tsup src/index.ts --format esm --watch",
|
"dev": "tsup src/index.ts src/templates/*.tsx --format esm --watch --dts",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-email/components": "^0.0.33",
|
"@react-email/components": "^0.0.33"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
25
packages/mail/src/index.test.tsx
Normal file
25
packages/mail/src/index.test.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import * as React from "react";
|
||||||
|
import { render } from "./index";
|
||||||
|
import { MintelLogo } from "./components/MintelLogo";
|
||||||
|
import { ContactFormNotification } from "./templates/ContactFormNotification";
|
||||||
|
|
||||||
|
describe("@mintel/mail rendering", () => {
|
||||||
|
it("should render the MintelLogo to HTML", async () => {
|
||||||
|
const html = await render(React.createElement(MintelLogo));
|
||||||
|
expect(html).toContain("Mintel Logo");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render a ContactFormNotification to HTML", async () => {
|
||||||
|
const html = await render(
|
||||||
|
React.createElement(ContactFormNotification, {
|
||||||
|
name: "Test User",
|
||||||
|
email: "test@example.com",
|
||||||
|
message: "Hello World",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(html).toContain("New Submission");
|
||||||
|
expect(html).toContain("Test User");
|
||||||
|
expect(html).toContain("test@example.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@@ -200,6 +200,9 @@ importers:
|
|||||||
'@mintel/tsconfig':
|
'@mintel/tsconfig':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../tsconfig
|
version: link:../tsconfig
|
||||||
|
'@tailwindcss/typography':
|
||||||
|
specifier: ^0.5.19
|
||||||
|
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.0.0
|
specifier: ^20.0.0
|
||||||
version: 20.19.30
|
version: 20.19.30
|
||||||
@@ -1832,6 +1835,11 @@ packages:
|
|||||||
'@swc/types@0.1.25':
|
'@swc/types@0.1.25':
|
||||||
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
|
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
|
||||||
|
|
||||||
|
'@tailwindcss/typography@0.5.19':
|
||||||
|
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
|
||||||
|
|
||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -3138,6 +3146,7 @@ packages:
|
|||||||
glob@9.3.5:
|
glob@9.3.5:
|
||||||
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
|
|
||||||
global-directory@4.0.1:
|
global-directory@4.0.1:
|
||||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||||
@@ -3741,6 +3750,7 @@ packages:
|
|||||||
next@15.1.6:
|
next@15.1.6:
|
||||||
resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==}
|
resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==}
|
||||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||||
|
deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@opentelemetry/api': ^1.1.0
|
'@opentelemetry/api': ^1.1.0
|
||||||
@@ -3991,6 +4001,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.2.14
|
postcss: ^8.2.14
|
||||||
|
|
||||||
|
postcss-selector-parser@6.0.10:
|
||||||
|
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
postcss-selector-parser@6.1.2:
|
postcss-selector-parser@6.1.2:
|
||||||
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -6498,6 +6512,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@swc/counter': 0.1.3
|
'@swc/counter': 0.1.3
|
||||||
|
|
||||||
|
'@tailwindcss/typography@0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
|
dependencies:
|
||||||
|
postcss-selector-parser: 6.0.10
|
||||||
|
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.2)
|
||||||
|
|
||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.28.6
|
'@babel/code-frame': 7.28.6
|
||||||
@@ -8890,6 +8909,11 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
postcss-selector-parser: 6.1.2
|
postcss-selector-parser: 6.1.2
|
||||||
|
|
||||||
|
postcss-selector-parser@6.0.10:
|
||||||
|
dependencies:
|
||||||
|
cssesc: 3.0.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
postcss-selector-parser@6.1.2:
|
postcss-selector-parser@6.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssesc: 3.0.0
|
cssesc: 3.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user