From 96fde71c6979a00dca06090c367c6f571b2573de Mon Sep 17 00:00:00 2001 From: Douglas DUTEIL Date: Mon, 14 Oct 2024 11:30:24 +0200 Subject: [PATCH] feat(email): storybook like email package --- .github/workflows/test.yml | 1 + .gitignore | 2 +- Dockerfile | 1 + package-lock.json | 353 ++++++++++------ package.json | 4 + packages/email/.storybook/ChangeView.tsx | 62 +++ .../.storybook/SendEmailFormWebComponent.tsx | 142 +++++++ packages/email/.storybook/index.html | 28 ++ packages/email/.storybook/index.tsx | 393 ++++++++++++++++++ packages/email/README.md | 50 +++ packages/email/index.html | 1 + packages/email/package.json | 21 + .../email/src/DeleteFreeTotpMail.stories.tsx | 17 + packages/email/src/DeleteFreeTotpMail.tsx | 40 ++ .../src/UpdatePersonalDataMail.stories.tsx | 53 +++ packages/email/src/UpdatePersonalDataMail.tsx | 63 +++ packages/email/src/_layout.tsx | 70 ++++ packages/email/src/components/Html.tsx | 34 ++ packages/email/src/components/Link.tsx | 26 ++ .../email/src/components/ProConnectLogo.tsx | 12 + packages/email/src/components/Section.tsx | 28 ++ packages/email/src/components/Text.tsx | 27 ++ packages/email/src/components/index.ts | 7 + packages/email/src/components/style.ts | 4 + packages/email/src/index.ts | 4 + packages/email/tsconfig.json | 30 ++ tsconfig.json | 1 + 27 files changed, 1355 insertions(+), 119 deletions(-) create mode 100644 packages/email/.storybook/ChangeView.tsx create mode 100644 packages/email/.storybook/SendEmailFormWebComponent.tsx create mode 100644 packages/email/.storybook/index.html create mode 100644 packages/email/.storybook/index.tsx create mode 100644 packages/email/README.md create mode 100644 packages/email/index.html create mode 100644 packages/email/package.json create mode 100644 packages/email/src/DeleteFreeTotpMail.stories.tsx create mode 100644 packages/email/src/DeleteFreeTotpMail.tsx create mode 100644 packages/email/src/UpdatePersonalDataMail.stories.tsx create mode 100644 packages/email/src/UpdatePersonalDataMail.tsx create mode 100644 packages/email/src/_layout.tsx create mode 100644 packages/email/src/components/Html.tsx create mode 100644 packages/email/src/components/Link.tsx create mode 100644 packages/email/src/components/ProConnectLogo.tsx create mode 100644 packages/email/src/components/Section.tsx create mode 100644 packages/email/src/components/Text.tsx create mode 100644 packages/email/src/components/index.ts create mode 100644 packages/email/src/components/style.ts create mode 100644 packages/email/src/index.ts create mode 100644 packages/email/tsconfig.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9a4b88e..fcbbd310 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,5 +21,6 @@ jobs: node-version-file: package.json - run: CYPRESS_INSTALL_BINARY=0 npm ci --include=dev - run: npm run test:lint + - run: npm run test:workspaces - run: npm run test:type-check - run: npm run test:unit diff --git a/.gitignore b/.gitignore index 48e9ce21..00fb84d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/node_modules +node_modules *.swp *.swo *.orig diff --git a/Dockerfile b/Dockerfile index bbc9c41b..2332f875 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY tsconfig.json vite.config.mjs ./ COPY assets/ ./assets/ COPY public/ ./public/ COPY src/ ./src/ +COPY packages/ ./packages/ COPY package*.json ./ RUN npx run-s build:* diff --git a/package-lock.json b/package-lock.json index 629f6437..93d77d1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "name": "moncomptepro", "version": "1.0.0", "license": "AGPL-3.0", + "workspaces": [ + "packages/*" + ], "dependencies": { "@dotenvx/dotenvx": "^1.19.2", "@fullhuman/postcss-purgecss": "^6.0.0", @@ -311,12 +314,13 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -326,12 +330,13 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -341,12 +346,13 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -356,12 +362,13 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -371,12 +378,13 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -386,12 +394,13 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -401,12 +410,13 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -416,12 +426,13 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -431,12 +442,13 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -446,12 +458,13 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -461,12 +474,13 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -476,12 +490,13 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -491,12 +506,13 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -506,12 +522,13 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -521,12 +538,13 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -536,12 +554,13 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -551,12 +570,13 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -566,12 +586,13 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -597,12 +618,13 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -612,12 +634,13 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -627,12 +650,13 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -642,12 +666,13 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -657,12 +682,13 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -810,6 +836,43 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@kitajs/html": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@kitajs/html/-/html-4.2.4.tgz", + "integrity": "sha512-wH/q9F+lYFAlI4pVVLtdndjF+dhinn8J4fJr+ofbH17/owVBi6g3/X4A8FgZETGuKnDoMP2ffO5xhQDxgtVuMA==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/kitajs/html?sponsor=1" + } + }, + "node_modules/@kitajs/ts-html-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@kitajs/ts-html-plugin/-/ts-html-plugin-4.1.0.tgz", + "integrity": "sha512-9e39FE/yhdmVsN0CmlvwefN1D+yaOOTz/30WVg2E1Pi7sEnv16EhbFvVUtqwV/bltSv9Wq9cp5oxI0gNv/ukYQ==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.7.0", + "yargs": "^17.7.2" + }, + "bin": { + "ts-html-plugin": "dist/cli.js", + "xss-scan": "dist/cli.js" + }, + "funding": { + "url": "https://github.com/kitajs/html?sponsor=1" + }, + "peerDependencies": { + "@kitajs/html": "^4.2.2", + "typescript": "^5.6.2" + } + }, "node_modules/@koa/cors": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz", @@ -906,6 +969,10 @@ "integrity": "sha512-fLa2Onk60hMclWoyKkpGpNSwPB3oyXg7fDmFB3t8I/b649IzDWsfYr7YFS9T0gYgaesw0hwhRKa8OT9LJZWIDg==", "license": "WTFPL" }, + "node_modules/@numerique-gouv/moncomptepro.email": { + "resolved": "packages/email", + "link": true + }, "node_modules/@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -1458,6 +1525,29 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "peer": true }, + "node_modules/@storybook/csf": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", + "integrity": "sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sunknudsen/totp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@sunknudsen/totp/-/totp-1.1.0.tgz", @@ -3129,6 +3219,12 @@ "node": ">=4" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, "node_modules/csv": { "version": "6.3.9", "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.9.tgz", @@ -3688,10 +3784,11 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -3699,29 +3796,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -6488,9 +6585,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -6555,9 +6653,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "funding": [ { "type": "opencollective", @@ -6572,10 +6670,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -7617,9 +7716,10 @@ "peer": true }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7900,9 +8000,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -8409,9 +8510,9 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8556,14 +8657,14 @@ } }, "node_modules/vite": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.14.tgz", - "integrity": "sha512-TFQLuwWLPms+NBNlh0D9LZQ+HXW471COABxw/9TEUBrjuHMo9BrYBPrN/SYAwIuVL+rLerycxiLT41t4f5MZpA==", + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", + "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", "license": "MIT", "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -8582,6 +8683,7 @@ "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -8599,6 +8701,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -8878,6 +8983,18 @@ "peerDependencies": { "zod": "^3.18.0" } + }, + "packages/email": { + "name": "@numerique-gouv/moncomptepro.email", + "version": "0.0.0", + "dependencies": { + "@kitajs/html": "^4.2.4", + "@kitajs/ts-html-plugin": "^4.1.0" + }, + "devDependencies": { + "@storybook/csf": "^0.1.11", + "vite": "^5.4.8" + } } } } diff --git a/package.json b/package.json index 9ccf9f44..fa566362 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,9 @@ "#cypress/*": "./cypress/*.ts" }, "main": "src/index.js", + "workspaces": [ + "packages/*" + ], "scripts": { "build": "run-s build:**", "build:assets": "vite build --clearScreen false", @@ -23,6 +26,7 @@ "test:lint": "prettier '**/*.ts' --list-different", "test:type-check": "tsc --noEmit", "test:unit": "NODE_ENV=test mocha --require tsx test/*.ts", + "test:workspaces": "npm run test --if-present --workspaces", "update-organization-info": "tsx scripts/update-organizations-info.ts", "watch:assets": "NODE_ENV=development vite build --clearScreen false --watch", "watch:ejs": "CHOKIDAR_USEPOLLING=true copy-and-watch --clean --watch \"src/views/**/*.ejs\" src/views/*.ejs build/views/", diff --git a/packages/email/.storybook/ChangeView.tsx b/packages/email/.storybook/ChangeView.tsx new file mode 100644 index 00000000..a26bf4d6 --- /dev/null +++ b/packages/email/.storybook/ChangeView.tsx @@ -0,0 +1,62 @@ +// + +export class ChangeView extends HTMLElement { + static tag = "x-change-view" as const; + static define() { + customElements.define(ChangeView.tag, this); + } + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + connectedCallback() { + this.#render(); + + this.#inputs.forEach((element) => { + element.addEventListener("click", () => { + this.#view = element.value as "desktop" | "html"; + }); + }); + } + #render() { + if (!this.shadowRoot) return; + this.shadowRoot.innerHTML = ( + + ).toString(); + } + + // + + static get current(): "desktop" | "html" { + const view = new URLSearchParams(location.search).get("view"); + return view === "html" ? view : "desktop"; + } + + set #view(value: "desktop" | "html") { + const url = new URL(location.href); + url.searchParams.set("view", value); + window.location.href = url.toString(); + } + + // + + get #inputs() { + if (!this.shadowRoot) throw new Error("ShadowRoot not found"); + return this.shadowRoot.querySelectorAll("button"); + } +} + +ChangeView.define(); + +function ChangeViewUI({ view }: { view: string }) { + return ( + + ); +} diff --git a/packages/email/.storybook/SendEmailFormWebComponent.tsx b/packages/email/.storybook/SendEmailFormWebComponent.tsx new file mode 100644 index 00000000..190e101e --- /dev/null +++ b/packages/email/.storybook/SendEmailFormWebComponent.tsx @@ -0,0 +1,142 @@ +// + +type SendEmailOptions = { + sender: { name: string; email: string }; + to: { + name: string; + email: string; + }[]; + subject: string; + htmlContent: string; +}; + +// + +export class SendEmailFormWebComponent extends HTMLElement { + static tag = "x-send-email-form" as const; + static define() { + customElements.define(SendEmailFormWebComponent.tag, this); + } + static BREVO_API_KEY = import.meta.env["VITE_BREVO_API_KEY"]; + + // + + constructor() { + super(); + this.attachShadow({ mode: "open" }); + } + + connectedCallback() { + if (!SendEmailFormWebComponent.BREVO_API_KEY) { + console.warn( + "No API key found for brevo, please set VITE_BREVO_API_KEY env variable if you want to test the email in a real email client environment", + ); + return; + } + + this.#render(); + + this.#form.addEventListener("submit", (e) => { + e.preventDefault(); + this.submit(this.innerHTML); + }); + } + + async submit(template: string) { + const headers = new Headers(); + headers.append("accept", "application/json"); + headers.append("api-key", SendEmailFormWebComponent.BREVO_API_KEY); + headers.append("content-type", "application/json"); + + await fetch("https://api.brevo.com/v3/smtp/email", { + method: "POST", + headers, + body: JSON.stringify({ + htmlContent: template, + sender: { + name: "MonComptePro", + email: "nepasrepondre@email.moncomptepro.beta.gouv.fr", + }, + subject: this.#object.value, + to: [{ name: "Ike Proconnect", email: this.#to.value }], + } as SendEmailOptions), + redirect: "follow", + }); + } + + // + + #render() { + if (!this.shadowRoot) return; + this.shadowRoot.innerHTML = ( +
+ + 📨 + + +
+ + +
+

+ Powered by Brevo +

+ +
+
+
+ ).toString(); + } + + get #form() { + const element = this.shadowRoot?.querySelector("form"); + if (!element) throw new Error("No form found"); + return element; + } + get #to() { + const element = this.shadowRoot?.querySelector("input[name=to]"); + if (!element) throw new Error("No input[name=to] found"); + return element as HTMLInputElement; + } + get #object() { + const element = this.shadowRoot?.querySelector("input[name=object]"); + if (!element) throw new Error("No input[name=object] found"); + return element as HTMLInputElement; + } +} + +SendEmailFormWebComponent.define(); diff --git a/packages/email/.storybook/index.html b/packages/email/.storybook/index.html new file mode 100644 index 00000000..16d0ce71 --- /dev/null +++ b/packages/email/.storybook/index.html @@ -0,0 +1,28 @@ + + + + + + Storybook like . Email Preview + + +
+ + + diff --git a/packages/email/.storybook/index.tsx b/packages/email/.storybook/index.tsx new file mode 100644 index 00000000..84c80344 --- /dev/null +++ b/packages/email/.storybook/index.tsx @@ -0,0 +1,393 @@ +// + +import type { Component, PropsWithChildren } from "@kitajs/html"; +import { escapeHtml } from "@kitajs/html"; +import type { + ComponentAnnotations, + Renderer, + StoryAnnotations, +} from "@storybook/csf"; +import { ChangeView } from "./ChangeView"; +import { SendEmailFormWebComponent } from "./SendEmailFormWebComponent"; + +// + +class NoFileException extends Error {} +class NoStoryMetaException extends Error {} +class VariantNotFoundException extends Error {} + +// + +document.getElementById("root")!.innerHTML = await Root(); +window.addEventListener("hashchange", async () => { + document.getElementById("root")!.innerHTML = await Root(); +}); + +// + +async function Root() { + return ( + + + +

Email Templates

+
+ + +
+
+ + + +
+
+ ); +} + +// + +function StoriesList() { + const stories = getStoriesFiles(); + return ( + + ); +} + +function StoriesListItem({ + value: [file, story], +}: { + value: [string, StoriesModule]; +}) { + const isActive = location.hash.slice(2).split("#")[0] === file; + const { default: defaultStory, ...variantStories } = story; + + return ( +
  • + + {defaultStory.title || file} + + +
  • + ); +} + +function VariantStoriesList({ + value: { file, stories }, +}: { + value: { file: string; stories: VariantStorie }; +}) { + const entries = Object.entries(stories) as [string, VariantStorie][]; + if (entries.length === 0) return null; + return ( + + ); +} + +function StoryLink( + props: PropsWithChildren<{ value: { file: string; key: string } }>, +) { + const { children } = props; + const { file, key } = props.value; + const href = `#/${file}#${key}`; + const isActive = location.hash === href; + return ( + + {children} + + ); +} + +async function TitleHeader() { + const file = getFileNameFromLocation(); + + if (!file) return <>; + const [story_err, story] = await getStory(file); + if (story_err) return <>; + return ( +
    + {story.default.title} + + +
    + ); +} + +function Preview() { + return ( +
    + + +
    + ); +} + +async function DesktopPreview() { + if (ChangeView.current !== "desktop") return <>; + return ( + + ); +} + +async function HTMLPreview() { + if (ChangeView.current !== "html") return <>; + const { format } = await import("prettier"); + const htmlParsers = await import("prettier/parser-html"); + const source = escapeHtml(await EmailContent()); + return ( + + ); +} + +async function EmailContent() { + const [template_err, template] = await getStoryTemplate(); + if (template_err instanceof NoFileException) { + return ; + } else if (template_err) { + return ( + + {template_err.name} +
    + {template_err.message} +
    + ); + } + return "" + template; +} + +// + +async function SendEmail() { + const [template_err, template] = await getStoryTemplate(); + if (template_err) return <>; + return {template}; +} + +// + +interface Meta extends ComponentAnnotations { + render: Component; +} + +type VariantStorie = StoryAnnotations; +type StoriesModule = { + default: Meta; +} & Partial>; + +function getStoriesFiles() { + return Object.entries( + import.meta.glob("../src/**/*.stories.tsx", { + eager: true, + }), + ) as [string, StoriesModule][]; +} + +async function getStory( + file: string, +): Promise<[Error, null] | [null, StoriesModule]> { + try { + return [null, await import(/* @vite-ignore */ file)]; + } catch (e) { + return [e as Error, null]; + } +} + +function getFileNameFromLocation() { + return location.hash.slice(2).split("#")[0]; +} + +function getVariantFromLocation() { + return location.hash.slice(2).split("#")[1]; +} + +export async function getStoryTemplate(): Promise< + [Error, null] | [null, string] +> { + const file = getFileNameFromLocation(); + if (!file) return [new NoFileException(), null]; + + const [story_err, story] = await getStory(file); + if (story_err) return [story_err, null]; + + const meta = story.default; + if (!meta.render) + return [ + new NoStoryMetaException(`"${file}" default might not be story...`), + null, + ]; + + const variant = getVariantFromLocation(); + const variantAnnotation = story[variant]; + if (variant && !variantAnnotation) { + return [ + new VariantNotFoundException(`Unknown variant "${variant}" in ${file}`), + null, + ]; + } + + const defaultArgs = meta.args || {}; + const args = { ...defaultArgs, ...variantAnnotation?.args }; + return [null, meta.render(args).toString()]; +} + +// + +function AppContainer({ children }: PropsWithChildren) { + return ( +
    + {children} +
    + ); +} +function Navbar({ children }: PropsWithChildren) { + return ( + + ); +} + +function NavbarHeader({ children }: PropsWithChildren) { + return
    {children}
    ; +} + +function Main({ children }: PropsWithChildren) { + return ( +
    + {children} +
    + ); +} + +function ErrorSection({ children }: PropsWithChildren) { + return ( +
    +      {children}
    +    
    + ); +} + +function Welcome() { + return ( + + +
    +

    Welcome to the Email Template "Storybook"

    + +

    🫲 Click on a story to see it in action.

    +
    + + + ); +} diff --git a/packages/email/README.md b/packages/email/README.md new file mode 100644 index 00000000..6f2a1f77 --- /dev/null +++ b/packages/email/README.md @@ -0,0 +1,50 @@ +# @numerique-gouv/moncomptepro.email + +## Usage + +```ts +import { DeleteFreeTotpMail } from "@numerique-gouv/moncomptepro.email"; + +const transporter = nodemailer.createTransport({ + host: "smtp.example.com", + port: 587, + secure: false, + auth: { + user: "user@example.com", + pass: "password", + }, +}); + +// [...] + +const info = await transporter.sendMail({ + from: "user@example.com", + to: "user@example.com", + subject: "[MonComptePro] Delete free TOTP", + html: DeleteFreeTotpMail({ + baseurl: my_host_name, + email: user.email, + userId: user.id, + }), +}); +``` + +## Development + +``` +# In this directory launch the dev server +$ npm run dev + + VITE vX.Y.Z ready in X ms + + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose + ➜ press h + enter to show help + +``` + +If you want to test with a real email service, you can use the `VITE_BREVO_API_KEY` environment variable: + +``` +$ VITE_BREVO_API_KEY=xxx npm run dev +``` diff --git a/packages/email/index.html b/packages/email/index.html new file mode 100644 index 00000000..6dd459e6 --- /dev/null +++ b/packages/email/index.html @@ -0,0 +1 @@ + diff --git a/packages/email/package.json b/packages/email/package.json new file mode 100644 index 00000000..4a8bd037 --- /dev/null +++ b/packages/email/package.json @@ -0,0 +1,21 @@ +{ + "name": "@numerique-gouv/moncomptepro.email", + "version": "0.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "dev": "vite", + "test": "tsc --noEmit" + }, + "dependencies": { + "@kitajs/html": "^4.2.4", + "@kitajs/ts-html-plugin": "^4.1.0" + }, + "devDependencies": { + "@storybook/csf": "^0.1.11", + "vite": "^5.4.8" + } +} diff --git a/packages/email/src/DeleteFreeTotpMail.stories.tsx b/packages/email/src/DeleteFreeTotpMail.stories.tsx new file mode 100644 index 00000000..31e35b97 --- /dev/null +++ b/packages/email/src/DeleteFreeTotpMail.stories.tsx @@ -0,0 +1,17 @@ +// + +import type { ComponentAnnotations, Renderer } from "@storybook/csf"; +import DeleteFreeTotpMail, { type Props } from "./DeleteFreeTotpMail"; + +// + +export default { + title: "Delete Free TOTP", + render: DeleteFreeTotpMail, + args: { + given_name: "Marie", + family_name: "Dupont", + support_email: "contact@moncomptepro.beta.gouv.fr", + baseurl: "http://localhost:3000", + }, +} as ComponentAnnotations; diff --git a/packages/email/src/DeleteFreeTotpMail.tsx b/packages/email/src/DeleteFreeTotpMail.tsx new file mode 100644 index 00000000..adfb7521 --- /dev/null +++ b/packages/email/src/DeleteFreeTotpMail.tsx @@ -0,0 +1,40 @@ +// + +import { Layout, type LayoutProps } from "./_layout"; +import { Link, Text } from "./components"; + +// + +export default function DeleteFreeTotpMail(props: Props) { + const { baseurl, given_name, family_name, support_email } = props; + const mailtoParams = new URLSearchParams({ + subject: "Erreur - Mon organisation", + }); + const mailtoHref = `mailto:${support_email}?${mailtoParams.toString()}`; + return ( + + + Bonjour {given_name} {family_name}, + +
    + + L'application a été supprimée comme étape de connexion à deux facteurs. +
    +
    + + Si vous n'avez pas supprimé cette application, quelqu'un utilise + peut-être votre compte. Faites-le nous savoir en répondant à cet + email. + +
    +
    + ); +} + +// + +export type Props = LayoutProps & { + given_name: string; + family_name: string; + support_email: string; +}; diff --git a/packages/email/src/UpdatePersonalDataMail.stories.tsx b/packages/email/src/UpdatePersonalDataMail.stories.tsx new file mode 100644 index 00000000..df781ed0 --- /dev/null +++ b/packages/email/src/UpdatePersonalDataMail.stories.tsx @@ -0,0 +1,53 @@ +// + +import type { + ComponentAnnotations, + Renderer, + StoryAnnotations, +} from "@storybook/csf"; +import UpdatePersonalDataMail, { type Props } from "./UpdatePersonalDataMail"; + +// +export default { + title: "Update Personal Data", + render: UpdatePersonalDataMail, + args: { + baseurl: "http://localhost:3000", + given_name: "Maarie", + family_name: "Duupont", + updatedFields: { + family_name: { + new: "Dupont", + old: "Duupont", + }, + given_name: { + new: "Marie", + old: "Maarie", + }, + job: { + new: "Cheffe de projet", + old: "Chef de projet", + }, + phone_number: { + new: "0123456789", + old: "9876543210", + }, + }, + }, +} as ComponentAnnotations; + +export const UpdateOnlyName: StoryAnnotations = { + name: "Update only names", + args: { + updatedFields: { + family_name: { + new: "Dupont", + old: "Dupont", + }, + given_name: { + new: "Marie", + old: "Marie", + }, + }, + }, +}; diff --git a/packages/email/src/UpdatePersonalDataMail.tsx b/packages/email/src/UpdatePersonalDataMail.tsx new file mode 100644 index 00000000..0fe53cbc --- /dev/null +++ b/packages/email/src/UpdatePersonalDataMail.tsx @@ -0,0 +1,63 @@ +// + +import { Layout, type LayoutProps } from "./_layout"; +import { Text } from "./components"; + +export default function UpdatePersonalDataMail(props: Props) { + const { baseurl, family_name, given_name, updatedFields } = props; + return ( + + + Bonjour {given_name} {family_name}, + +
    + + Nous vous informons que vos données personnelles ont été mises à jour + avec succès. +
    +
    + Les données modifiées sont les suivantes : +
    +
      + {updatedFields.given_name && ( +
    • + Prénom : {updatedFields.given_name.new} +
    • + )} + {updatedFields.family_name && ( +
    • + Nom de famille : {updatedFields.family_name.new} +
    • + )} + {updatedFields.phone_number && ( +
    • + + Numéro de téléphone : {updatedFields.phone_number.new} + +
    • + )} + {updatedFields.job && ( +
    • + + Profession ou rôle au sein de votre organisation :{" "} + {updatedFields.job.new} + +
    • + )} +
    +
    + ); +} + +// + +export type Props = LayoutProps & { + family_name: string | null; + given_name: string | null; + updatedFields: Partial<{ + family_name: { new: string | null; old: string | null }; + given_name: { new: string | null; old: string | null }; + job: { new: string | null; old: string | null }; + phone_number: { new: string | null; old: string | null }; + }>; +}; diff --git a/packages/email/src/_layout.tsx b/packages/email/src/_layout.tsx new file mode 100644 index 00000000..806b0f1c --- /dev/null +++ b/packages/email/src/_layout.tsx @@ -0,0 +1,70 @@ +// + +import type { PropsWithChildren } from "@kitajs/html"; +import { Html, ProConnectLogo, Section, Text } from "./components"; + +// + +export interface LayoutProps { + baseurl: string; +} + +export function VSpacing({ height }: { height: number }) { + return ( +
    +   +
    + ); +} +export function Layout({ children }: PropsWithChildren) { + return ( + +
    + + +
    + +
    + + + +
    + + +
    + {children} + + + Cordialement, +
    + L'équipe ProConnect +
    +
    + + +
    + + +
    + + + ); +} diff --git a/packages/email/src/components/Html.tsx b/packages/email/src/components/Html.tsx new file mode 100644 index 00000000..f32d7f30 --- /dev/null +++ b/packages/email/src/components/Html.tsx @@ -0,0 +1,34 @@ +// + +import type { PropsWithChildren } from "@kitajs/html"; + +// + +export function Html(attributes: PropsWithChildren) { + const { children, ...props } = attributes; + return ( + + + + + + + + + + + + +
    + {children} +
    + + + ); +} diff --git a/packages/email/src/components/Link.tsx b/packages/email/src/components/Link.tsx new file mode 100644 index 00000000..66c32cb0 --- /dev/null +++ b/packages/email/src/components/Link.tsx @@ -0,0 +1,26 @@ +// + +import type { PropsWithChildren } from "@kitajs/html"; +import { fontFamily } from "./style"; + +// + +export function Link(attributes: PropsWithChildren) { + const { children, href, ...props } = attributes; + return ( + + {children} + + ); +} diff --git a/packages/email/src/components/ProConnectLogo.tsx b/packages/email/src/components/ProConnectLogo.tsx new file mode 100644 index 00000000..8a8839c4 --- /dev/null +++ b/packages/email/src/components/ProConnectLogo.tsx @@ -0,0 +1,12 @@ +// + +export function ProConnectLogo() { + return ( + ProConnect Logo + ); +} diff --git a/packages/email/src/components/Section.tsx b/packages/email/src/components/Section.tsx new file mode 100644 index 00000000..cc1344e2 --- /dev/null +++ b/packages/email/src/components/Section.tsx @@ -0,0 +1,28 @@ +// + +import type { PropsWithChildren } from "@kitajs/html"; + +// + +export function Section(attributes: PropsWithChildren) { + const { align, children, style, ...props } = attributes; + + return ( + + + + + + +
    {children}
    + ); +} diff --git a/packages/email/src/components/Text.tsx b/packages/email/src/components/Text.tsx new file mode 100644 index 00000000..f33fc192 --- /dev/null +++ b/packages/email/src/components/Text.tsx @@ -0,0 +1,27 @@ +// + +import type { PropsWithChildren } from "@kitajs/html"; +import { fontFamily } from "./style"; + +// + +export function Text(attributes: PropsWithChildren) { + const { children, style, ...props } = attributes; + + return ( +

    + {children} +

    + ); +} diff --git a/packages/email/src/components/index.ts b/packages/email/src/components/index.ts new file mode 100644 index 00000000..753e2aa2 --- /dev/null +++ b/packages/email/src/components/index.ts @@ -0,0 +1,7 @@ +// + +export * from "./Html"; +export * from "./Link"; +export * from "./ProConnectLogo"; +export * from "./Section"; +export * from "./Text"; diff --git a/packages/email/src/components/style.ts b/packages/email/src/components/style.ts new file mode 100644 index 00000000..e2973da4 --- /dev/null +++ b/packages/email/src/components/style.ts @@ -0,0 +1,4 @@ +// + +export const fontFamily = + "'Source Sans Pro', Verdana, Geneva, Arial, sans-serif"; diff --git a/packages/email/src/index.ts b/packages/email/src/index.ts new file mode 100644 index 00000000..81cde342 --- /dev/null +++ b/packages/email/src/index.ts @@ -0,0 +1,4 @@ +// + +export { default as DeleteFreeTotpMail } from "./DeleteFreeTotpMail"; +export { default as UpdatePersonalDataMail } from "./UpdatePersonalDataMail"; diff --git a/packages/email/tsconfig.json b/packages/email/tsconfig.json new file mode 100644 index 00000000..e8f326f9 --- /dev/null +++ b/packages/email/tsconfig.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/node20/tsconfig.json", + "compilerOptions": { + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-jsx", + "jsxImportSource": "@kitajs/html", + "lib": ["ES2023", "DOM"], + "module": "Preserve", + "moduleResolution": "Bundler", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "./build", + "plugins": [{ "name": "@kitajs/ts-html-plugin" }], + "preserveSymlinks": true, + "preserveWatchOutput": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "strictNullChecks": true, + "target": "ESNext", + "types": ["vite/client"], + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", ".storybook/**/*.tsx"] +} diff --git a/tsconfig.json b/tsconfig.json index 093fbd18..61fa99de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,6 @@ "rootDir": ".", "verbatimModuleSyntax": true }, + "exclude": ["packages/*"], "extends": "@tsconfig/node20/tsconfig.json" }