mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
chore: cleaner setup
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/eslint-config/nextjs"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/blog",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@ -37,7 +37,7 @@
|
||||
"react-icons": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
|
@ -1,36 +0,0 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configConventions from "eslint-config-conventions"
|
||||
import importX from "eslint-plugin-import-x"
|
||||
|
||||
export default typescriptESLint.config(
|
||||
{
|
||||
ignores: [
|
||||
".next",
|
||||
"**/next.config.js",
|
||||
"**/eslint.config.js",
|
||||
"**/tailwind.config.js",
|
||||
"**/postcss.config.js",
|
||||
"**/vitest.config.ts",
|
||||
"**/kysely.config.ts",
|
||||
],
|
||||
},
|
||||
...configConventions,
|
||||
{
|
||||
name: "config-eslint",
|
||||
plugins: {
|
||||
"import-x": importX,
|
||||
},
|
||||
rules: {
|
||||
"import-x/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{
|
||||
ts: "always",
|
||||
tsx: "always",
|
||||
js: "never",
|
||||
jsx: "never",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
7
packages/config-eslint/index.d.ts
vendored
7
packages/config-eslint/index.d.ts
vendored
@ -1,7 +0,0 @@
|
||||
import type typescriptESLint from "typescript-eslint"
|
||||
|
||||
declare const eslintConfigConventions: ReturnType<
|
||||
typeof typescriptESLint.config
|
||||
>
|
||||
|
||||
export default eslintConfigConventions
|
@ -1,61 +0,0 @@
|
||||
import { FlatCompat } from "@eslint/eslintrc"
|
||||
import storybook from "eslint-plugin-storybook"
|
||||
import tailwind from "eslint-plugin-tailwindcss"
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import config from "../eslint.config.js"
|
||||
|
||||
const flatCompat = new FlatCompat()
|
||||
|
||||
export default typescriptESLint.config(
|
||||
...config,
|
||||
...flatCompat.extends("next/core-web-vitals"),
|
||||
...tailwind.configs["flat/recommended"],
|
||||
...storybook.configs["flat/recommended"],
|
||||
{
|
||||
name: "config-eslint/nextjs",
|
||||
settings: {
|
||||
tailwindcss: {
|
||||
callees: ["classNames", "cva"],
|
||||
},
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"tailwindcss/classnames-order": "off",
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
"react/self-closing-comp": [
|
||||
"error",
|
||||
{
|
||||
component: true,
|
||||
html: true,
|
||||
},
|
||||
],
|
||||
"react/void-dom-elements-no-children": "error",
|
||||
"react/jsx-boolean-value": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "next/link",
|
||||
message: "Please import from `@repo/i18n/routing` instead.",
|
||||
},
|
||||
{
|
||||
name: "next/navigation",
|
||||
importNames: [
|
||||
"redirect",
|
||||
"permanentRedirect",
|
||||
"useRouter",
|
||||
"usePathname",
|
||||
],
|
||||
message: "Please import from `@repo/i18n/routing` instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "@repo/eslint-config",
|
||||
"version": "4.1.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./eslint.config.js",
|
||||
"require": "./eslint.config.js",
|
||||
"default": "./eslint.config.js"
|
||||
},
|
||||
"./nextjs": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./nextjs/eslint.config.js",
|
||||
"require": "./nextjs/eslint.config.js",
|
||||
"default": "./nextjs/eslint.config.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"eslint-config-conventions": "catalog:",
|
||||
"eslint-plugin-promise": "catalog:",
|
||||
"eslint-plugin-unicorn": "catalog:",
|
||||
"eslint-config-next": "catalog:",
|
||||
"eslint-plugin-storybook": "catalog:",
|
||||
"eslint-plugin-tailwindcss": "catalog:",
|
||||
"eslint-plugin-import-x": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"globals": "catalog:"
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export const classNames = (...inputs: ClassValue[]): string => {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import config from "@repo/eslint-config"
|
||||
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptESLint.parser,
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
3
packages/config-tailwind/index.d.ts
vendored
3
packages/config-tailwind/index.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
|
||||
export default Config
|
@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "@repo/config-tailwind",
|
||||
"version": "4.1.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./tailwind.config.js",
|
||||
"types": "./index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./tailwind.config.js",
|
||||
"require": "./tailwind.config.js",
|
||||
"default": "./tailwind.config.js"
|
||||
},
|
||||
"./classNames": "./classNames.ts",
|
||||
"./styles.css": "./styles.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/montserrat": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"tailwind-merge": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@tailwindcss/typography": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"postcss": "catalog:",
|
||||
"tailwindcss": "catalog:"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
@ -1,303 +0,0 @@
|
||||
@import "@fontsource/montserrat/400.css";
|
||||
@import "@fontsource/montserrat/500.css";
|
||||
@import "@fontsource/montserrat/600.css";
|
||||
@import "@fontsource/montserrat/700.css";
|
||||
@import "@fontsource/montserrat/800.css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
i,
|
||||
em {
|
||||
@apply italic;
|
||||
}
|
||||
|
||||
u {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
s {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
@apply underline decoration-dotted underline-offset-2;
|
||||
}
|
||||
|
||||
q,
|
||||
blockquote {
|
||||
@apply italic tracking-wider;
|
||||
}
|
||||
blockquote {
|
||||
@apply border-gray-lighter border-l-4 pl-3 italic;
|
||||
}
|
||||
|
||||
kbd {
|
||||
@apply bg-gray-lighter rounded-md px-2 dark:text-black;
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-yellow rounded-md px-2;
|
||||
}
|
||||
|
||||
ol {
|
||||
@apply list-inside list-decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply list-inside list-disc;
|
||||
}
|
||||
|
||||
dfn {
|
||||
@apply font-semibold italic;
|
||||
cursor: help;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background dark:bg-background-dark font-sans text-black dark:text-white;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
.break-wrap-words {
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.text-base {
|
||||
@apply leading-8;
|
||||
}
|
||||
|
||||
.prose {
|
||||
@apply dark:text-gray-lighter !max-w-5xl scroll-smooth text-black;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
@apply text-justify;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
.prose ol {
|
||||
@apply list-outside;
|
||||
}
|
||||
|
||||
.prose [id]::before {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 90px;
|
||||
margin-top: -90px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
@apply text-primary dark:text-primary-dark !font-semibold;
|
||||
}
|
||||
|
||||
.prose strong {
|
||||
@apply dark:text-gray-lighter text-black;
|
||||
}
|
||||
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6 {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
color: #ce9178;
|
||||
}
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::before,
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::after {
|
||||
content: "";
|
||||
}
|
||||
.shiki {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
html.dark .shiki,
|
||||
html.dark .shiki span {
|
||||
color: var(--shiki-dark) !important;
|
||||
background-color: var(--shiki-dark-bg) !important;
|
||||
font-style: var(--shiki-dark-font-style) !important;
|
||||
font-weight: var(--shiki-dark-font-weight) !important;
|
||||
text-decoration: var(--shiki-dark-text-decoration) !important;
|
||||
}
|
||||
|
||||
code {
|
||||
counter-reset: step;
|
||||
counter-increment: step 0;
|
||||
}
|
||||
code .line::before {
|
||||
content: counter(step);
|
||||
counter-increment: step;
|
||||
margin-right: 1rem;
|
||||
text-align: right;
|
||||
color: rgba(133, 133, 133, 0.8);
|
||||
word-wrap: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.katex .base {
|
||||
display: inline !important;
|
||||
white-space: normal !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.curriculum-vitae {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
hr {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
border: 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
a {
|
||||
color: #337ab7;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:focus,
|
||||
a:hover {
|
||||
color: #23527c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.link-disguise {
|
||||
color: inherit;
|
||||
}
|
||||
.link-disguise:hover {
|
||||
color: inherit;
|
||||
}
|
||||
.h1,
|
||||
.h2,
|
||||
.h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.h4,
|
||||
.h5,
|
||||
.h6 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.h1,
|
||||
.h2,
|
||||
.h3,
|
||||
.h4,
|
||||
.h5,
|
||||
.h6 {
|
||||
font-family: inherit;
|
||||
line-height: 1.1;
|
||||
color: inherit;
|
||||
}
|
||||
.h3 {
|
||||
font-size: 24px;
|
||||
}
|
||||
.h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.text-muted {
|
||||
color: #414141;
|
||||
}
|
||||
.list-unstyled {
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.card-wrapper {
|
||||
float: none !important;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.profile-pic {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.profile-pic img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
}
|
||||
.social-links {
|
||||
line-height: 2.5;
|
||||
}
|
||||
|
||||
.background-details .detail {
|
||||
display: table;
|
||||
}
|
||||
.background-details .detail .icon,
|
||||
.background-details .detail .info {
|
||||
display: table-cell;
|
||||
}
|
||||
.background-details .detail .icon {
|
||||
color: #707070;
|
||||
}
|
||||
.background-details .detail .icon {
|
||||
min-width: 45px;
|
||||
max-width: 45px;
|
||||
text-align: center;
|
||||
}
|
||||
.icon img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.background-details .detail .mobile-title {
|
||||
display: none;
|
||||
}
|
||||
.card-nested {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.labels {
|
||||
line-height: 2;
|
||||
}
|
||||
.label {
|
||||
display: inline;
|
||||
padding: 0.2em 0.6em 0.3em;
|
||||
font-size: 75%;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: 0.25em;
|
||||
}
|
||||
.label-keyword {
|
||||
display: inline-block;
|
||||
font-size: 0.9em;
|
||||
padding: 5px;
|
||||
border: 1px solid #357ebd;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.label-keyword p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import typographyPlugin from "@tailwindcss/typography"
|
||||
|
||||
/** @type {Omit<import('tailwindcss').Config, "content">} */
|
||||
const config = {
|
||||
darkMode: "selector",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: "#0056b3",
|
||||
dark: "#00aeff",
|
||||
},
|
||||
background: {
|
||||
DEFAULT: "#fff",
|
||||
dark: "#181818",
|
||||
},
|
||||
"gray-lighter": "#d1d5db",
|
||||
"gray-darker": {
|
||||
DEFAULT: "#4b5563",
|
||||
dark: "#9ca3af",
|
||||
},
|
||||
yellow: "#fef08a",
|
||||
},
|
||||
boxShadow: {
|
||||
dark: "0px 0px 2px 2px rgba(0, 0, 0, 0.25)",
|
||||
light: "0px 0px 2px 2px rgba(0, 0, 0, 0.10)",
|
||||
darkFlag: "0px 1px 10px hsla(0, 0%, 100%, 0.2)",
|
||||
lightFlag: "0px 1px 10px rgba(0, 0, 0, 0.25)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Montserrat", "sans-serif"],
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
a: {
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
fontWeight: 400,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [typographyPlugin],
|
||||
}
|
||||
|
||||
export default config
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "@repo/config-typescript/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"]
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@repo/config-typescript",
|
||||
"version": "4.1.3",
|
||||
"private": true,
|
||||
"files": [
|
||||
"tsconfig.json"
|
||||
]
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnreachableCode": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noEmit": true,
|
||||
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import config from "@repo/eslint-config"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/i18n",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@ -11,26 +11,23 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit",
|
||||
"test": "vitest run"
|
||||
"lint:typescript": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@repo/utils": "workspace:*",
|
||||
"deepmerge": "catalog:",
|
||||
"next": "catalog:",
|
||||
"next-intl": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
4
packages/i18n/src/messages.d.ts
vendored
4
packages/i18n/src/messages.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
import type en from "./translations/en-US.json"
|
||||
import type fr from "./translations/fr-FR.json"
|
||||
|
||||
type Messages = typeof en
|
||||
type Messages = typeof fr
|
||||
|
||||
declare global {
|
||||
/**
|
||||
|
@ -1,9 +1,9 @@
|
||||
import deepmerge from "deepmerge"
|
||||
import type { AbstractIntlMessages } from "next-intl"
|
||||
import { getRequestConfig } from "next-intl/server"
|
||||
|
||||
import type { Locale } from "@repo/utils/constants"
|
||||
import { LOCALE_DEFAULT, LOCALES } from "@repo/utils/constants"
|
||||
import { deepMerge } from "@repo/utils/objects"
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
let locale = await requestLocale
|
||||
@ -15,7 +15,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
||||
const defaultMessages = (
|
||||
await import(`./translations/${LOCALE_DEFAULT}.json`)
|
||||
).default
|
||||
const messages = deepmerge<AbstractIntlMessages>(
|
||||
const messages = deepMerge<AbstractIntlMessages>(
|
||||
defaultMessages,
|
||||
userMessages,
|
||||
)
|
||||
|
@ -4,6 +4,13 @@ import { LOCALES, LOCALE_DEFAULT, LOCALE_PREFIX } from "@repo/utils/constants"
|
||||
import { defineRouting } from "next-intl/routing"
|
||||
import type { Locale } from "@repo/utils/constants"
|
||||
|
||||
// Countries: https://github.com/umpirsky/country-list/blob/master/data/en/country.json
|
||||
// Country flag picture: https://purecatamphetamine.github.io/country-flag-icons/3x2/US.svg
|
||||
|
||||
// Locale codes: https://simplelocalize.io/data/locales/
|
||||
// Locale code is a combination of ISO 639-1 language code and ISO 3166-1 country code.
|
||||
// For example, `fr-FR` is a locale code for French language in France.
|
||||
|
||||
export interface LocaleProps {
|
||||
params: Promise<{
|
||||
locale: Locale
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { expectTypeOf, test } from "vitest"
|
||||
import en from "../translations/en-US.json"
|
||||
import fr from "../translations/fr-FR.json"
|
||||
|
||||
test("translations types should match", () => {
|
||||
expectTypeOf(en).toEqualTypeOf(fr)
|
||||
})
|
@ -1,4 +1,24 @@
|
||||
{
|
||||
"meta": {
|
||||
"description": "Developer Full Stack • Open-Source Enthusiast",
|
||||
"title": "Théo LUDWIG"
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "English",
|
||||
"fr-FR": "French"
|
||||
},
|
||||
"loading": "Loading...",
|
||||
"errors": {
|
||||
"error": "Error",
|
||||
"not-found": "Not Found",
|
||||
"page-doesnt-exist": "This page doesn't exist!",
|
||||
"return-to-home-page": "Return to the home page?",
|
||||
"server-error": "Internal Server Error!",
|
||||
"try-again": "Try again?"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "All rights reserved"
|
||||
},
|
||||
"curriculum-vitae": {
|
||||
"about": {
|
||||
"description": "I constantly wonder how to improve our present, to make our future better, particularly thanks to the advancements in computer science. <br></br> My priority is to craft intuitive user experiences (UX), that meet the needs of the users in the most efficient way possible.",
|
||||
@ -84,17 +104,6 @@
|
||||
"title": "Work experiences"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"error": "Error",
|
||||
"not-found": "Not Found",
|
||||
"page-doesnt-exist": "This page doesn't exist!",
|
||||
"return-to-home-page": "Return to the home page?",
|
||||
"server-error": "Internal Server Error!",
|
||||
"try-again": "Try again?"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "All rights reserved"
|
||||
},
|
||||
"home": {
|
||||
"about": {
|
||||
"birth-date": {
|
||||
@ -150,13 +159,5 @@
|
||||
"software-tools": "Software and tools",
|
||||
"title": "Skills"
|
||||
}
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "English",
|
||||
"fr-FR": "French"
|
||||
},
|
||||
"meta": {
|
||||
"description": "Developer Full Stack • Open-Source Enthusiast",
|
||||
"title": "Théo LUDWIG"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,24 @@
|
||||
{
|
||||
"meta": {
|
||||
"description": "Développeur Full Stack • Enthousiaste de l'Open-Source",
|
||||
"title": "Théo LUDWIG"
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "Anglais",
|
||||
"fr-FR": "Français"
|
||||
},
|
||||
"loading": "Chargement...",
|
||||
"errors": {
|
||||
"error": "Erreur",
|
||||
"not-found": "Introuvable",
|
||||
"page-doesnt-exist": "Cette page n'existe pas !",
|
||||
"return-to-home-page": "Retour à la page d'accueil ?",
|
||||
"server-error": "Erreur interne du serveur !",
|
||||
"try-again": "Réessayer ?"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "Tous droits réservés"
|
||||
},
|
||||
"curriculum-vitae": {
|
||||
"about": {
|
||||
"description": "Je me demande constamment comment améliorer notre présent, afin de rendre notre futur meilleur, particulièrement grâce aux progrès de l'informatique. <br></br> Ma priorité réside dans la création d'expériences utilisateurs (UX) intuitives, répondant aux besoins des utilisateurs de la manière la plus efficace que possible.",
|
||||
@ -84,17 +104,6 @@
|
||||
"title": "Expériences professionnelles"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"error": "Erreur",
|
||||
"not-found": "Introuvable",
|
||||
"page-doesnt-exist": "Cette page n'existe pas !",
|
||||
"return-to-home-page": "Retour à la page d'accueil ?",
|
||||
"server-error": "Erreur interne du serveur !",
|
||||
"try-again": "Réessayer ?"
|
||||
},
|
||||
"footer": {
|
||||
"all-rights-reserved": "Tous droits réservés"
|
||||
},
|
||||
"home": {
|
||||
"about": {
|
||||
"birth-date": {
|
||||
@ -150,13 +159,5 @@
|
||||
"software-tools": "Logiciels et outils",
|
||||
"title": "Compétences"
|
||||
}
|
||||
},
|
||||
"locales": {
|
||||
"en-US": "Anglais",
|
||||
"fr-FR": "Français"
|
||||
},
|
||||
"meta": {
|
||||
"description": "Développeur Full Stack • Enthousiaste de l'Open-Source",
|
||||
"title": "Théo LUDWIG"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
typecheck: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/eslint-config/nextjs"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/react-hooks",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@ -9,28 +9,21 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit",
|
||||
"test": "vitest run --browser.headless",
|
||||
"test:ui": "vitest --ui --no-open"
|
||||
"lint:typescript": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@testing-library/react": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"@vitest/browser": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"@vitest/ui": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"playwright": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"typescript-eslint": "catalog:"
|
||||
}
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
import { act, renderHook } from "@testing-library/react"
|
||||
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { useBoolean } from "../useBoolean.ts"
|
||||
|
||||
describe("useBoolean", () => {
|
||||
const initialValues = [true, false]
|
||||
|
||||
for (const initialValue of initialValues) {
|
||||
it(`should set the initial value to ${initialValue}`, () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useBoolean({ initialValue })
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(initialValue)
|
||||
})
|
||||
}
|
||||
|
||||
it("should by default set the initial value to false", () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useBoolean()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(false)
|
||||
})
|
||||
|
||||
it("should toggle the value", () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useBoolean({ initialValue: false })
|
||||
})
|
||||
|
||||
// Act - When
|
||||
act(() => {
|
||||
return result.current.toggle()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(true)
|
||||
|
||||
// Act - When
|
||||
act(() => {
|
||||
return result.current.toggle()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(false)
|
||||
})
|
||||
|
||||
it("should set the value to true", () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useBoolean({ initialValue: false })
|
||||
})
|
||||
|
||||
// Act - When
|
||||
act(() => {
|
||||
return result.current.setTrue()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(true)
|
||||
})
|
||||
|
||||
it("should set the value to false", () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useBoolean({ initialValue: true })
|
||||
})
|
||||
|
||||
// Act - When
|
||||
act(() => {
|
||||
return result.current.setFalse()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.value).toBe(false)
|
||||
})
|
||||
})
|
@ -1,16 +0,0 @@
|
||||
import { renderHook } from "@testing-library/react"
|
||||
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { useIsMounted } from "../useIsMounted.ts"
|
||||
|
||||
describe("useIsMounted", () => {
|
||||
it("should return true", () => {
|
||||
// Arrange - Given
|
||||
const { result } = renderHook(() => {
|
||||
return useIsMounted()
|
||||
})
|
||||
|
||||
// Assert - Then
|
||||
expect(result.current.isMounted).toBe(true)
|
||||
})
|
||||
})
|
@ -18,11 +18,11 @@ export interface UseBooleanInput {
|
||||
|
||||
/**
|
||||
* Hook to manage a boolean state.
|
||||
* @param options
|
||||
* @param input
|
||||
* @returns
|
||||
*/
|
||||
export const useBoolean = (options: UseBooleanInput = {}): UseBooleanOutput => {
|
||||
const { initialValue = false } = options
|
||||
export const useBoolean = (input: UseBooleanInput = {}): UseBooleanOutput => {
|
||||
const { initialValue = false } = input
|
||||
|
||||
const [value, setValue] = useState(initialValue)
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
include: ["@vitest/coverage-v8/browser"],
|
||||
},
|
||||
test: {
|
||||
browser: {
|
||||
provider: "playwright",
|
||||
enabled: true,
|
||||
name: "chromium",
|
||||
screenshotFailures: false,
|
||||
},
|
||||
coverage: {
|
||||
enabled: true,
|
||||
provider: "v8",
|
||||
},
|
||||
},
|
||||
})
|
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/eslint-config/nextjs"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@repo/ui",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
@ -40,7 +40,7 @@
|
||||
"react-icons": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import config from "@repo/eslint-config"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
|
@ -1,29 +1,28 @@
|
||||
{
|
||||
"name": "@repo/utils",
|
||||
"version": "4.1.3",
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./constants": "./src/constants.ts",
|
||||
"./dates": "./src/dates.ts",
|
||||
"./strings": "./src/strings.ts"
|
||||
"./objects": "./src/objects.ts",
|
||||
"./strings": "./src/strings.ts",
|
||||
"./types": "./src/types.ts",
|
||||
"./urls": "./src/urls.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:ui": "vitest --ui --no-open"
|
||||
"test": "node --experimental-strip-types --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@types/node": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"@vitest/coverage-v8": "catalog:",
|
||||
"@vitest/ui": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"typescript-eslint": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const THEMES = ["light", "dark"] as const
|
||||
export type Theme = (typeof THEMES)[number]
|
||||
export const THEME_DEFAULT = "light" satisfies Theme
|
||||
|
||||
export const TIMEZONE = process.env["TZ"] ?? "UTC"
|
||||
export const TIMEZONE = process.env["TZ"] ?? "Europe/Paris"
|
||||
|
||||
export const BIRTH_DATE_DAY = "31"
|
||||
export const BIRTH_DATE_MONTH = "03"
|
||||
|
19
packages/utils/src/objects.ts
Normal file
19
packages/utils/src/objects.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export const deepMerge = <
|
||||
Object1 extends object,
|
||||
Object2 extends object = Object1,
|
||||
>(
|
||||
object1: Object1,
|
||||
object2: Object2,
|
||||
): Object1 & Object2 => {
|
||||
const result = { ...object1 } as Object1 & Object2
|
||||
for (const key in object2) {
|
||||
if (Object.hasOwn(object2, key)) {
|
||||
if (typeof object2[key] === "object" && object2[key] !== null) {
|
||||
result[key] = deepMerge(result[key] as any, object2[key] as any)
|
||||
} else {
|
||||
result[key] = object2[key] as any
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
describe("VERSION", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs()
|
||||
vi.resetModules()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('should return "0.0.0-development" when NODE_ENV is development', async () => {
|
||||
// Arrange - Given
|
||||
vi.stubEnv("NODE_ENV", "development")
|
||||
|
||||
// Act - When
|
||||
const { VERSION } = await import("../constants.ts")
|
||||
|
||||
// Assert - Then
|
||||
const expected = "0.0.0-development"
|
||||
expect(VERSION).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the version from package.json when NODE_ENV is not development", async () => {
|
||||
// Arrange - Given
|
||||
vi.stubEnv("NODE_ENV", "production")
|
||||
vi.mock("../../package.json", () => {
|
||||
return { default: { version: "1.0.0" } }
|
||||
})
|
||||
|
||||
// Act - When
|
||||
const { VERSION } = await import("../constants.ts")
|
||||
|
||||
// Assert - Then
|
||||
const expected = "1.0.0"
|
||||
expect(VERSION).toEqual(expected)
|
||||
})
|
||||
})
|
@ -1,79 +1,19 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { getISODate } from "../dates.ts"
|
||||
|
||||
import { getAge, getISODate } from "../dates.ts"
|
||||
describe("dates", () => {
|
||||
describe("getISODate", () => {
|
||||
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
||||
// Arrange - Given
|
||||
const input = new Date("2012-05-23")
|
||||
|
||||
describe("getISODate", () => {
|
||||
it("should return the correct date in ISO format (e.g: 2012-05-23)", () => {
|
||||
// Arrange - Given
|
||||
const input = new Date("2012-05-23")
|
||||
// Act - When
|
||||
const output = getISODate(input)
|
||||
|
||||
// Act - When
|
||||
const output = getISODate(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "2012-05-23"
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getAge", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-02-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 38
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday has not happened yet", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-07-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 37
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday is today", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-03-20")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 38
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
|
||||
it("should return the correct age based on the birth date when the birthday has not happened yet, but will happen this month", () => {
|
||||
// Arrange - Given
|
||||
vi.setSystemTime(new Date("2018-03-20"))
|
||||
const birthDate = new Date("1980-03-25")
|
||||
|
||||
// Act - When
|
||||
const output = getAge(birthDate)
|
||||
|
||||
// Assert - Then
|
||||
const expected = 37
|
||||
expect(output).toEqual(expected)
|
||||
// Assert - Then
|
||||
const expected = "2012-05-23"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
85
packages/utils/src/tests/objects.test.ts
Normal file
85
packages/utils/src/tests/objects.test.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { deepMerge } from "../objects.ts"
|
||||
|
||||
describe("objects", () => {
|
||||
describe("deepMerge", () => {
|
||||
it("should merge two simple objects", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: 2 }
|
||||
const object2 = { b: 3, c: 4 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 3, c: 4 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should deeply merge nested objects", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: { x: 2, y: 3 } }
|
||||
const object2 = { b: { y: 4, z: 5 }, c: 6 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: { x: 2, y: 4, z: 5 }, c: 6 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should overwrite primitive values", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: "hello" }
|
||||
const object2 = { a: 2, b: "world" }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 2, b: "world" }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the second object if the first is empty", () => {
|
||||
// Arrange - Given
|
||||
const object1 = {}
|
||||
const object2 = { a: 1, b: 2 }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 2 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the first object if the second is empty", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: 2 }
|
||||
const object2 = {}
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: 2 }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should handle null and undefined values correctly", () => {
|
||||
// Arrange - Given
|
||||
const object1 = { a: 1, b: null }
|
||||
const object2 = { b: { c: 2 }, d: undefined }
|
||||
|
||||
// Act - When
|
||||
const output = deepMerge(object1, object2)
|
||||
|
||||
// Assert - Then
|
||||
const expected = { a: 1, b: { c: 2 }, d: undefined }
|
||||
assert.deepStrictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
@ -1,41 +1,43 @@
|
||||
import { describe, expect, it } from "vitest"
|
||||
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { capitalize } from "../strings.ts"
|
||||
|
||||
describe("capitalize", () => {
|
||||
it("should capitalize the first letter of a string", () => {
|
||||
// Arrange - Given
|
||||
const input = "hello, world!"
|
||||
describe("strings", () => {
|
||||
describe("capitalize", () => {
|
||||
it("should capitalize the first letter of a string", () => {
|
||||
// Arrange - Given
|
||||
const input = "hello, world!"
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return an empty string when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
it("should return an empty string when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = ""
|
||||
expect(output).toEqual(expected)
|
||||
})
|
||||
// Assert - Then
|
||||
const expected = ""
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same string when the first letter is already capitalized", () => {
|
||||
// Arrange - Given
|
||||
const input = "Hello, world!"
|
||||
it("should return the same string when the first letter is already capitalized", () => {
|
||||
// Arrange - Given
|
||||
const input = "Hello, world!"
|
||||
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
// Act - When
|
||||
const output = capitalize(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
expect(output).toEqual(expected)
|
||||
// Assert - Then
|
||||
const expected = "Hello, world!"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
80
packages/utils/src/tests/urls.test.ts
Normal file
80
packages/utils/src/tests/urls.test.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { LOCALE_DEFAULT } from "../constants.ts"
|
||||
import { getPathnameWithoutLocale } from "../urls.ts"
|
||||
|
||||
describe("urls", () => {
|
||||
describe("getPathnameWithoutLocale", () => {
|
||||
it("should return the pathname without the known locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}/about`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same pathname when the input does not start with a known locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = "/about"
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the same pathname when the input starts with an unknown locale prefix", () => {
|
||||
// Arrange - Given
|
||||
const input = "/abc-ABC/about"
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/abc-ABC/about"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input is an empty string", () => {
|
||||
// Arrange - Given
|
||||
const input = ""
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input starts with a known locale prefix and with a trailing slash", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}/`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
|
||||
it("should return the index route when the input starts with a known locale prefix and without a trailing slash", () => {
|
||||
// Arrange - Given
|
||||
const input = `/${LOCALE_DEFAULT}`
|
||||
|
||||
// Act - When
|
||||
const output = getPathnameWithoutLocale(input)
|
||||
|
||||
// Assert - Then
|
||||
const expected = "/"
|
||||
assert.strictEqual(output, expected)
|
||||
})
|
||||
})
|
||||
})
|
49
packages/utils/src/types.ts
Normal file
49
packages/utils/src/types.ts
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
|
||||
*/
|
||||
export type Primitive =
|
||||
| null
|
||||
| undefined
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| bigint
|
||||
|
||||
export type Satisfies<U, T extends U> = T
|
||||
|
||||
export type OmitStrict<T, K extends keyof T> = Omit<T, K>
|
||||
export type PickStrict<T, K extends keyof T> = Pick<T, K>
|
||||
|
||||
export type OverrideStrict<
|
||||
Type,
|
||||
NewType extends {
|
||||
[Key in keyof Type]?: unknown
|
||||
},
|
||||
> = Omit<Type, keyof NewType> & NewType
|
||||
|
||||
export type PartialDeep<T> = T extends object
|
||||
? {
|
||||
[P in keyof T]?: PartialDeep<T[P]>
|
||||
}
|
||||
: T
|
||||
|
||||
export type Status = "error" | "idle" | "pending" | "success"
|
||||
|
||||
/**
|
||||
* Allows creating a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union.
|
||||
*
|
||||
* @see https://github.com/Microsoft/TypeScript/issues/29729
|
||||
*
|
||||
* @example
|
||||
```
|
||||
// Before
|
||||
type Pet = 'dog' | 'cat' | string;
|
||||
|
||||
// After
|
||||
type Pet2 = LiteralUnion<'dog' | 'cat', string>;
|
||||
```
|
||||
*/
|
||||
export type LiteralUnion<LiteralType, BaseType extends Primitive> =
|
||||
| LiteralType
|
||||
| (BaseType & Record<never, never>)
|
18
packages/utils/src/urls.ts
Normal file
18
packages/utils/src/urls.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { LOCALES } from "./constants.ts"
|
||||
|
||||
/**
|
||||
* Get the pathname without the known locale prefix.
|
||||
* @param input
|
||||
* @returns
|
||||
* @example getRoutePathnameWithoutLocale("/fr-FR/about") // "/about"
|
||||
*/
|
||||
export const getPathnameWithoutLocale = (input: string): string => {
|
||||
const locale = LOCALES.find((locale) => {
|
||||
return input.startsWith(`/${locale}`)
|
||||
})
|
||||
const pathname = locale != null ? input.slice(locale.length + 1) : input
|
||||
if (pathname.length <= 0) {
|
||||
return `/${pathname}`
|
||||
}
|
||||
return pathname
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
coverage: {
|
||||
enabled: true,
|
||||
provider: "v8",
|
||||
},
|
||||
},
|
||||
})
|
Reference in New Issue
Block a user