mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -04:00
feat(app): tools management base
This commit is contained in:
parent
202896fa95
commit
b22173681c
29 changed files with 1372 additions and 45 deletions
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 98%;
|
||||||
--foreground: 240 10% 3.9%;
|
--foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 240 10% 3.9%;
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
--primary: 240 5.9% 10%;
|
--primary: 150 76% 38%;
|
||||||
--primary-foreground: 0 0% 98%;
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
--secondary: 240 4.8% 95.9%;
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
@ -36,10 +36,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background:0 0% 9%;
|
--background:240 5% 6%;
|
||||||
--foreground:0 0% 98%;
|
--foreground:0 0% 98%;
|
||||||
|
|
||||||
--card: 0 0% 7%;
|
--card: 240 5% 8%;
|
||||||
--card-foreground:0 0% 98%;
|
--card-foreground:0 0% 98%;
|
||||||
|
|
||||||
--popover:240 10% 3.9%;
|
--popover:240 10% 3.9%;
|
||||||
|
@ -74,4 +74,4 @@
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,5 @@
|
||||||
export default defineI18nConfig(() => ({
|
export default defineI18nConfig(() => ({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
messages: {
|
fallbackLocale: 'en',
|
||||||
en: {
|
|
||||||
welcome: 'Welcome',
|
|
||||||
},
|
|
||||||
fr: {
|
|
||||||
welcome: 'Bienvenue',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import toolsModule from './src/modules/tools/modules/tools.modules';
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: '2024-04-03',
|
compatibilityDate: '2024-04-03',
|
||||||
|
@ -14,9 +16,18 @@ export default defineNuxtConfig({
|
||||||
'@nuxt/icon',
|
'@nuxt/icon',
|
||||||
'@vueuse/nuxt',
|
'@vueuse/nuxt',
|
||||||
'@nuxtjs/color-mode',
|
'@nuxtjs/color-mode',
|
||||||
|
toolsModule, // Must be imported before i18n
|
||||||
'@nuxtjs/i18n',
|
'@nuxtjs/i18n',
|
||||||
|
'@nuxtjs/seo',
|
||||||
|
'@pinia/nuxt',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
site: {
|
||||||
|
url: 'https://it-tools.tech',
|
||||||
|
name: 'IT Tools',
|
||||||
|
description: 'The open-source collection of handy online tools to help developers in their daily life.',
|
||||||
|
},
|
||||||
|
|
||||||
fonts: {
|
fonts: {
|
||||||
provider: 'bunny',
|
provider: 'bunny',
|
||||||
defaults: {
|
defaults: {
|
||||||
|
@ -35,7 +46,11 @@ export default defineNuxtConfig({
|
||||||
i18n: {
|
i18n: {
|
||||||
strategy: 'prefix',
|
strategy: 'prefix',
|
||||||
vueI18n: './i18n.config.ts',
|
vueI18n: './i18n.config.ts',
|
||||||
locales: ['en', 'fr'],
|
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
|
langDir: './src/locales',
|
||||||
|
locales: [
|
||||||
|
{ code: 'en', file: 'en.yaml', name: 'English' },
|
||||||
|
{ code: 'fr', file: 'fr.yaml', name: 'Français' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@9.12.2",
|
"packageManager": "pnpm@9.12.2",
|
||||||
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
|
@ -19,12 +18,16 @@
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@corentinth/chisels": "^1.1.0",
|
||||||
"@nuxt/fonts": "^0.10.2",
|
"@nuxt/fonts": "^0.10.2",
|
||||||
"@nuxt/icon": "^1.5.6",
|
"@nuxt/icon": "^1.5.6",
|
||||||
"@nuxtjs/color-mode": "^3.5.2",
|
"@nuxtjs/color-mode": "^3.5.2",
|
||||||
"@nuxtjs/i18n": "^8.5.5",
|
"@nuxtjs/i18n": "^8.5.5",
|
||||||
|
"@nuxtjs/seo": "2.0.0-rc.23",
|
||||||
|
"@pinia/nuxt": "^0.5.5",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-vue-next": "^0.453.0",
|
"lucide-vue-next": "^0.453.0",
|
||||||
"nuxt": "^3.13.2",
|
"nuxt": "^3.13.2",
|
||||||
"radix-vue": "^1.9.7",
|
"radix-vue": "^1.9.7",
|
||||||
|
@ -37,6 +40,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "^3.8.0",
|
"@antfu/eslint-config": "^3.8.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@vueuse/core": "^11.1.0",
|
"@vueuse/core": "^11.1.0",
|
||||||
"@vueuse/nuxt": "^11.1.0",
|
"@vueuse/nuxt": "^11.1.0",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.13.0",
|
||||||
|
|
4
packages/app/public/humans.txt
Normal file
4
packages/app/public/humans.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/* TEAM */
|
||||||
|
Developer: Corentin Thomasset
|
||||||
|
Site: https://corentin.tech
|
||||||
|
Twitter: @cthmsst
|
|
@ -1 +0,0 @@
|
||||||
|
|
31
packages/app/src/locales/en.yaml
Normal file
31
packages/app/src/locales/en.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
app:
|
||||||
|
title: IT-Tools
|
||||||
|
description: The open-source collection of handy online tools to help developers in their daily life.
|
||||||
|
home:
|
||||||
|
all-the-tools: All the tools
|
||||||
|
search-tools: Search for a tool
|
||||||
|
open-source: Open Source
|
||||||
|
free: Free
|
||||||
|
self-hostable: Self-hostable
|
||||||
|
open-tool: Open tool
|
||||||
|
footer:
|
||||||
|
resources:
|
||||||
|
title: Resources
|
||||||
|
all-tools: All the tools
|
||||||
|
github: GitHub repository
|
||||||
|
support: Support IT-Tools
|
||||||
|
license: License
|
||||||
|
support:
|
||||||
|
title: Support
|
||||||
|
report-bug: Report a bug
|
||||||
|
request-feature: Request a feature
|
||||||
|
contribute: Contribute to the project
|
||||||
|
contact: Contact me
|
||||||
|
friends:
|
||||||
|
title: Friends
|
||||||
|
tools:
|
||||||
|
token-generator:
|
||||||
|
title: Token Generator
|
||||||
|
description: >-
|
||||||
|
Generate random string with the characters you want, uppercase, lowercase
|
||||||
|
letters, numbers and/or symbols.
|
29
packages/app/src/locales/fr.yaml
Normal file
29
packages/app/src/locales/fr.yaml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
app:
|
||||||
|
title: IT-Tools
|
||||||
|
description: La collection open-source d'outils en ligne pour aider les devs dans leur vie quotidienne.
|
||||||
|
home:
|
||||||
|
all-the-tools: Tous les outils
|
||||||
|
search-tools: Rechercher un outil
|
||||||
|
open-source: Open Source
|
||||||
|
free: Gratuit
|
||||||
|
self-hostable: Self-hostable
|
||||||
|
open-tool: Ouvrir l'outil
|
||||||
|
footer:
|
||||||
|
resources:
|
||||||
|
title: Ressources
|
||||||
|
all-tools: Tous les outils
|
||||||
|
github: Dépôt GitHub
|
||||||
|
support: Soutenir IT-Tools
|
||||||
|
license: Licence
|
||||||
|
support:
|
||||||
|
title: Support
|
||||||
|
report-bug: Signaler un bug
|
||||||
|
request-feature: Demander une fonctionnalité
|
||||||
|
contribute: Contribuer au projet
|
||||||
|
contact: Me contacter
|
||||||
|
friends:
|
||||||
|
title: Ami·e·s
|
||||||
|
tools:
|
||||||
|
token-generator:
|
||||||
|
title: Générateur de token
|
||||||
|
description: Générer des chaines de caractères aléatoires, contrôlez les caractères que vous voulez, lettres majuscules, minuscules, chiffres et/ou symboles.
|
|
@ -1,29 +1,32 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
const localePath = useLocalePath();
|
const localePath = useLocalePath();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const sections = computed(() => [
|
const sections = computed(() => [
|
||||||
{
|
{
|
||||||
title: 'Lorem',
|
title: t('footer.resources.title'),
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Foo', to: '/foo' },
|
{ label: t('footer.resources.all-tools'), to: localePath('/tools') },
|
||||||
{ label: 'Bar', to: '/bar' },
|
{ label: t('footer.resources.github'), href: 'https://github.com/CorentinTh/it-tools' },
|
||||||
{ label: 'Baz', to: '/baz' },
|
{ label: t('footer.resources.support'), href: 'https://buymeacoffee.com/cthmsst' },
|
||||||
|
{ label: 'Humans.txt', href: '/humans.txt' },
|
||||||
|
{ label: t('footer.resources.license'), href: 'https://github.com/CorentinTh/it-tools/blob/main/LICENSE' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Ipsum',
|
title: t('footer.support.title'),
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Foo', to: '/foo' },
|
{ label: t('footer.support.report-bug'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||||
{ label: 'Bar', to: '/bar' },
|
{ label: t('footer.support.request-feature'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||||
{ label: 'Baz', to: '/baz' },
|
{ label: t('footer.support.contribute'), href: 'https://github.com/CorentinTh/it-tools/blob/main/CONTRIBUTING.md' },
|
||||||
|
{ label: t('footer.support.contact'), href: 'https://github.com/CorentinTh/it-tools/issues/new/choose' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Dolor',
|
title: t('footer.friends.title'),
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Foo', to: '/foo' },
|
{ label: 'Jugly.io', href: 'https://jugly.io' },
|
||||||
{ label: 'Bar', to: '/bar' },
|
{ label: 'Enclosed.cc', href: 'https://enclosed.cc' },
|
||||||
{ label: 'Baz', to: '/baz' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ const socialLinks = [
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<footer class="bg-card border-t border-border">
|
<footer class="light:bg-muted/50 dark:bg-black/20 mt-12">
|
||||||
<div class="py-12 px-6 max-w-screen-xl mx-auto ">
|
<div class="py-12 px-6 max-w-screen-xl mx-auto ">
|
||||||
<div class="flex items-start justify-between flex-col md:flex-row gap-12">
|
<div class="flex items-start justify-between flex-col md:flex-row gap-12">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -6,7 +6,7 @@ const colorMode = useColorMode();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full border-b">
|
<div class="w-full border-b bg-card">
|
||||||
<div class="max-w-screen-xl mx-auto flex items-center justify-between py-2 px-6">
|
<div class="max-w-screen-xl mx-auto flex items-center justify-between py-2 px-6">
|
||||||
<NuxtLink variant="link" class="text-xl font-semibold border-b border-transparent hover:no-underline h-auto px-1 rounded-none !transition-border-color-250" :as="Button" :to="localePath('/')" aria-label="Home">
|
<NuxtLink variant="link" class="text-xl font-semibold border-b border-transparent hover:no-underline h-auto px-1 rounded-none !transition-border-color-250" :as="Button" :to="localePath('/')" aria-label="Home">
|
||||||
<span class="font-bold text-foreground">IT</span>
|
<span class="font-bold text-foreground">IT</span>
|
||||||
|
|
|
@ -5,7 +5,7 @@ const localePath = useLocalePath();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center text-center">
|
<div class="flex justify-center text-center mt-24">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-light text-muted-foreground">
|
<h1 class="text-3xl font-light text-muted-foreground">
|
||||||
404
|
404
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Badge } from '@/src/modules/ui/components/badge';
|
import { Badge } from '@/src/modules/ui/components/badge';
|
||||||
import { Button } from '@/src/modules/ui/components/button';
|
import { Button, buttonVariants } from '@/src/modules/ui/components/button';
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/src/modules/ui/components/dropdown-menu';
|
import { cn } from '../../shared/style/cn';
|
||||||
|
import { useToolsStore } from '../../tools/tools.store';
|
||||||
|
import { CardContent } from '../../ui/components/card';
|
||||||
|
import Card from '../../ui/components/card/Card.vue';
|
||||||
|
|
||||||
|
const { tools } = useToolsStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -10,16 +15,13 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||||
<div class="max-w-xl">
|
<div class="max-w-xl">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||||
<!-- {{ $t('landing.hero.badges.open-source') }} -->
|
{{ $t('home.open-source') }}
|
||||||
Open Source
|
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||||
<!-- {{ $t('landing.hero.badges.free') }} -->
|
{{ $t('home.free') }}
|
||||||
Free
|
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
<Badge class="text-primary bg-primary/10 hover:bg-primary/10">
|
||||||
<!-- {{ $t('landing.hero.badges.self-hostable') }} -->
|
{{ $t('home.self-hostable') }}
|
||||||
Self-hostable
|
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -29,21 +31,18 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="text-xl text-gray-400 mb-4">
|
<p class="text-xl text-gray-400 mb-4">
|
||||||
<!-- {{ $t('app.description') }} -->
|
{{ $t('app.description') }}
|
||||||
The open-source collection of handy online tools to help developers in their daily life.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<Button>
|
<Button>
|
||||||
<!-- {{ $t('landing.hero.all-the-tools') }} -->
|
{{ $t('home.all-the-tools') }}
|
||||||
All the tools
|
|
||||||
<Icon name="i-tabler-arrow-right" class="ml-2 size-4" />
|
<Icon name="i-tabler-arrow-right" class="ml-2 size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
<!-- {{ $t('landing.hero.search-tools') }} -->
|
|
||||||
<Icon name="i-tabler-search" class="mr-2 size-4" />
|
<Icon name="i-tabler-search" class="mr-2 size-4" />
|
||||||
Search tools
|
{{ $t('home.search-tools') }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -54,4 +53,22 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</grid-background>
|
</grid-background>
|
||||||
|
|
||||||
|
<div class="max-w-screen-xl mx-auto px-6">
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
|
||||||
|
<NuxtLink v-for="tool in tools" :key="tool.key" :to="tool.path">
|
||||||
|
<Card class="p-6 h-full cursor-pointer hover:shadow-lg transition hover:translate-y-[-2px]">
|
||||||
|
<Icon :name="tool.icon" class="size-12 text-muted-foreground/60" />
|
||||||
|
|
||||||
|
<div class="font-semibold text-base">
|
||||||
|
{{ tool.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-muted-foreground mt-2">
|
||||||
|
{{ tool.description }}
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// @vitest-environment nuxt
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { useRefreshableState } from './useRefreshableState';
|
||||||
|
|
||||||
|
describe('useRefreshableState composables', () => {
|
||||||
|
describe('useRefreshableState', () => {
|
||||||
|
test('the tuple provided by useRefreshableState contain the state that is the result of the provided function and a refresh function', () => {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
const [state, refresh] = useRefreshableState('key', () => ++index);
|
||||||
|
|
||||||
|
expect(state.value).to.equal(1);
|
||||||
|
expect(index).to.equal(1);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
expect(state.value).to.equal(2);
|
||||||
|
expect(index).to.equal(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { get } from '@vueuse/core';
|
||||||
|
|
||||||
|
export function useRefreshableState<T>(key: string, getState: () => T | Ref<T>) {
|
||||||
|
const state = useState(key, getState);
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
const value = getState();
|
||||||
|
state.value = get(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [state, refresh] as const;
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { sample as sampleImpl, times } from 'lodash-es';
|
||||||
|
|
||||||
|
export function createToken({
|
||||||
|
withUppercase = true,
|
||||||
|
withLowercase = true,
|
||||||
|
withNumbers = true,
|
||||||
|
withSymbols = false,
|
||||||
|
length = 64,
|
||||||
|
alphabet,
|
||||||
|
sample = sampleImpl,
|
||||||
|
}: {
|
||||||
|
withUppercase?: boolean;
|
||||||
|
withLowercase?: boolean;
|
||||||
|
withNumbers?: boolean;
|
||||||
|
withSymbols?: boolean;
|
||||||
|
length?: number;
|
||||||
|
alphabet?: string;
|
||||||
|
sample?: (str: string) => string;
|
||||||
|
}) {
|
||||||
|
const allAlphabet = alphabet ?? [
|
||||||
|
withUppercase ? 'ABCDEFGHIJKLMOPQRSTUVWXYZ' : '',
|
||||||
|
withLowercase ? 'abcdefghijklmopqrstuvwxyz' : '',
|
||||||
|
withNumbers ? '0123456789' : '',
|
||||||
|
withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : '',
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
return times(length, () => sample(allAlphabet)).join('');
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineTool } from '../../tools.models';
|
||||||
|
|
||||||
|
export const tokenGeneratorTool = defineTool({
|
||||||
|
slug: 'token-generator',
|
||||||
|
entryFile: './token-generator.vue',
|
||||||
|
icon: 'i-tabler-key',
|
||||||
|
createdAt: new Date('2024-02-13'),
|
||||||
|
currentDirUrl: import.meta.url,
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRefreshableState } from '~/src/modules/shared/composables/useRefreshableState';
|
||||||
|
import { Button } from '~/src/modules/ui/components/button';
|
||||||
|
import { createToken } from './token-generator.models';
|
||||||
|
|
||||||
|
const withUppercase = ref(true);
|
||||||
|
const withLowercase = ref(true);
|
||||||
|
const withNumbers = ref(true);
|
||||||
|
const withSymbols = ref(false);
|
||||||
|
const length = ref(64);
|
||||||
|
|
||||||
|
const [token, refreshToken] = useRefreshableState(
|
||||||
|
'token-generator:token',
|
||||||
|
() => createToken({
|
||||||
|
withUppercase: withUppercase.value,
|
||||||
|
withLowercase: withLowercase.value,
|
||||||
|
withNumbers: withNumbers.value,
|
||||||
|
withSymbols: withSymbols.value,
|
||||||
|
length: length.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken);
|
||||||
|
|
||||||
|
// const { copy: copyToken } = useCopy({ source: token, notificationText: 'Token copied to clipboard' });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="max-w-screen-md mx-auto p-6">
|
||||||
|
<div>{{ token }}</div>
|
||||||
|
|
||||||
|
<Button class="mt-4" @click="refreshToken">
|
||||||
|
Generate new token
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
21
packages/app/src/modules/tools/modules/tools.modules.ts
Normal file
21
packages/app/src/modules/tools/modules/tools.modules.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { defineNuxtModule, extendPages } from '@nuxt/kit';
|
||||||
|
import { toolDefinitions } from '../tools.registry';
|
||||||
|
|
||||||
|
export default defineNuxtModule({
|
||||||
|
meta: {
|
||||||
|
name: 'tools',
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
extendPages((pages) => {
|
||||||
|
pages.push(...toolDefinitions.map((tool) => {
|
||||||
|
return {
|
||||||
|
path: `/${tool.slug}`,
|
||||||
|
file: tool.entryFile,
|
||||||
|
meta: {
|
||||||
|
toolKey: tool.key,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
18
packages/app/src/modules/tools/tools.models.ts
Normal file
18
packages/app/src/modules/tools/tools.models.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export function defineTool(toolDefinition: {
|
||||||
|
slug: string;
|
||||||
|
entryFile: string;
|
||||||
|
currentDirUrl: string;
|
||||||
|
icon: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}) {
|
||||||
|
const entryFile = new URL(toolDefinition.entryFile, toolDefinition.currentDirUrl).pathname;
|
||||||
|
const baseGithubUrlPath = entryFile.match(/(\/tools\/.*)$/)?.[1];
|
||||||
|
const entryFileGithubUrl = `https://github.com/CorentinTh/crucials-tools/blob/main${baseGithubUrlPath}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...toolDefinition,
|
||||||
|
key: toolDefinition.slug,
|
||||||
|
entryFile,
|
||||||
|
entryFileGithubUrl,
|
||||||
|
};
|
||||||
|
}
|
5
packages/app/src/modules/tools/tools.registry.ts
Normal file
5
packages/app/src/modules/tools/tools.registry.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { tokenGeneratorTool } from './definitions/token-generator/token-generator.tool';
|
||||||
|
|
||||||
|
export const toolDefinitions = [
|
||||||
|
tokenGeneratorTool,
|
||||||
|
];
|
36
packages/app/src/modules/tools/tools.store.ts
Normal file
36
packages/app/src/modules/tools/tools.store.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { joinUrlPaths } from '@corentinth/chisels';
|
||||||
|
import { toolDefinitions } from './tools.registry';
|
||||||
|
|
||||||
|
export const useToolsStore = defineStore('tools', () => {
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
|
const localizedTools = computed(() => {
|
||||||
|
return toolDefinitions.map((tool) => {
|
||||||
|
const { key, slug } = tool;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tool,
|
||||||
|
title: t(`tools.${key}.title`),
|
||||||
|
description: t(`tools.${key}.description`),
|
||||||
|
path: `/${joinUrlPaths(locale.value, slug)}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tools: localizedTools,
|
||||||
|
getToolByKey({ key }: { key: unknown }) {
|
||||||
|
if (typeof key !== 'string') {
|
||||||
|
throw new TypeError('Invalid key');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = localizedTools.value.find(tool => tool.key === key);
|
||||||
|
|
||||||
|
if (!tool) {
|
||||||
|
throw new Error('Tool not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return tool;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
21
packages/app/src/modules/ui/components/card/Card.vue
Normal file
21
packages/app/src/modules/ui/components/card/Card.vue
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'rounded-xl border bg-card text-card-foreground shadow',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
14
packages/app/src/modules/ui/components/card/CardContent.vue
Normal file
14
packages/app/src/modules/ui/components/card/CardContent.vue
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('p-6 pt-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p :class="cn('text-sm text-muted-foreground', props.class)">
|
||||||
|
<slot />
|
||||||
|
</p>
|
||||||
|
</template>
|
14
packages/app/src/modules/ui/components/card/CardFooter.vue
Normal file
14
packages/app/src/modules/ui/components/card/CardFooter.vue
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('flex items-center p-6 pt-0', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
14
packages/app/src/modules/ui/components/card/CardHeader.vue
Normal file
14
packages/app/src/modules/ui/components/card/CardHeader.vue
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
18
packages/app/src/modules/ui/components/card/CardTitle.vue
Normal file
18
packages/app/src/modules/ui/components/card/CardTitle.vue
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { cn } from '@/src/modules/shared/style/cn'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h3
|
||||||
|
:class="
|
||||||
|
cn('font-semibold leading-none tracking-tight', props.class)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</h3>
|
||||||
|
</template>
|
6
packages/app/src/modules/ui/components/card/index.ts
Normal file
6
packages/app/src/modules/ui/components/card/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export { default as Card } from './Card.vue'
|
||||||
|
export { default as CardContent } from './CardContent.vue'
|
||||||
|
export { default as CardDescription } from './CardDescription.vue'
|
||||||
|
export { default as CardFooter } from './CardFooter.vue'
|
||||||
|
export { default as CardHeader } from './CardHeader.vue'
|
||||||
|
export { default as CardTitle } from './CardTitle.vue'
|
945
pnpm-lock.yaml
generated
945
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue