diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..bd6f5fe --- /dev/null +++ "b/\\" @@ -0,0 +1,90 @@ + + + + + + + List of Entries + + + Name + Last Accident + + + + + {{ entry.name }} + {{ getDifferenceToToday(entry.last_reset) }} + + + + + + + + + + + + + + + + + Create new Entry + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + diff --git a/package.json b/package.json index 83b4c56..8d50725 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,19 @@ "build:sw": "workbox generateSW workbox-config.js" }, "dependencies": { + "@vee-validate/zod": "^4.12.5", + "@vueuse/core": "^10.9.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-vue-next": "^0.344.0", + "moment": "^2.30.1", "radix-vue": "^1.4.9", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", - "vue": "^3.4.19" + "vaul-vue": "^0.1.0", + "vee-validate": "^4.12.5", + "vue": "^3.4.19", + "zod": "^3.22.4" }, "devDependencies": { "@types/node": "^20.11.24", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bcca27..dd986e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@vee-validate/zod': + specifier: ^4.12.5 + version: 4.12.5(vue@3.4.19) + '@vueuse/core': + specifier: ^10.9.0 + version: 10.9.0(vue@3.4.19) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -14,6 +20,9 @@ dependencies: lucide-vue-next: specifier: ^0.344.0 version: 0.344.0(vue@3.4.19) + moment: + specifier: ^2.30.1 + version: 2.30.1 radix-vue: specifier: ^1.4.9 version: 1.4.9(vue@3.4.19) @@ -23,9 +32,18 @@ dependencies: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.1) + vaul-vue: + specifier: ^0.1.0 + version: 0.1.0(typescript@5.3.3) + vee-validate: + specifier: ^4.12.5 + version: 4.12.5(vue@3.4.19) vue: specifier: ^3.4.19 version: 3.4.19(typescript@5.3.3) + zod: + specifier: ^3.22.4 + version: 3.22.4 devDependencies: '@types/node': @@ -1792,6 +1810,20 @@ packages: resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} dev: true + /@types/web-bluetooth@0.0.20: + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + dev: false + + /@vee-validate/zod@4.12.5(vue@3.4.19): + resolution: {integrity: sha512-hUjvXaa4HHvlZeosucViIDOUikQmyKaXXuL6P8LR1ETOUrBV6ntTsafJGvRYtwhXosoLYuolUD6Km737okK4Gg==} + dependencies: + type-fest: 4.11.1 + vee-validate: 4.12.5(vue@3.4.19) + zod: 3.22.4 + transitivePeerDependencies: + - vue + dev: false + /@vitejs/plugin-vue@5.0.4(vite@5.1.4)(vue@3.4.19): resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -1856,6 +1888,10 @@ packages: '@vue/compiler-dom': 3.4.19 '@vue/shared': 3.4.19 + /@vue/devtools-api@6.6.1: + resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==} + dev: false + /@vue/language-core@1.8.27(typescript@5.3.3): resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} peerDependencies: @@ -1912,6 +1948,31 @@ packages: '@vue/compiler-core': 3.4.19 dev: true + /@vueuse/core@10.9.0(vue@3.4.19): + resolution: {integrity: sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==} + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.9.0 + '@vueuse/shared': 10.9.0(vue@3.4.19) + vue-demi: 0.14.7(vue@3.4.19) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/metadata@10.9.0: + resolution: {integrity: sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==} + dev: false + + /@vueuse/shared@10.9.0(vue@3.4.19): + resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==} + dependencies: + vue-demi: 0.14.7(vue@3.4.19) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -3294,6 +3355,10 @@ packages: hasBin: true dev: true + /moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + dev: false + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true @@ -4205,6 +4270,11 @@ packages: engines: {node: '>=10'} dev: true + /type-fest@4.11.1: + resolution: {integrity: sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==} + engines: {node: '>=16'} + dev: false + /typed-array-buffer@1.0.2: resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} engines: {node: '>= 0.4'} @@ -4327,6 +4397,27 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + /vaul-vue@0.1.0(typescript@5.3.3): + resolution: {integrity: sha512-3PYWMbN3cSdsciv3fzewskxZFnX61PYq1uNsbvizXDo/8sN4SMrWkYDqWaPdTD3GTEm6wpx7j5flRLg7A5ZXbQ==} + dependencies: + '@vueuse/core': 10.9.0(vue@3.4.19) + radix-vue: 1.4.9(vue@3.4.19) + vue: 3.4.19(typescript@5.3.3) + transitivePeerDependencies: + - '@vue/composition-api' + - typescript + dev: false + + /vee-validate@4.12.5(vue@3.4.19): + resolution: {integrity: sha512-rvaDfLPSLwTk+mf016XWE4drB8yXzOsKXiKHTb9gNXNLTtQSZ0Ww26O0/xbIFQe+n3+u8Wv1Y8uO/aLDX4fxOg==} + peerDependencies: + vue: ^3.3.11 + dependencies: + '@vue/devtools-api': 6.6.1 + type-fest: 4.11.1 + vue: 3.4.19(typescript@5.3.3) + dev: false + /vite-plugin-pwa@0.19.0(vite@5.1.4)(workbox-build@7.0.0)(workbox-window@7.0.0): resolution: {integrity: sha512-Unfb4Jk/ka4HELtpMLIPCmGcW4LFT+CL7Ri1/Of1544CVKXS2ftP91kUkNzkzeI1sGpOdVGuxprVLB9NjMoCAA==} engines: {node: '>=16.0.0'} @@ -4677,4 +4768,3 @@ packages: /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: true diff --git a/src/App.vue b/src/App.vue index 5afb7f3..b4dc808 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,103 @@ - This is a cool button + + + + List of Entries + + + Name + Last Accident + + + + + {{ entry.name }} + {{ getDifferenceToToday(entry.last_reset) }} + + + + + + + + + + + + + + + + + Create new Entry + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + - + diff --git a/src/components/ui/drawer/Drawer.vue b/src/components/ui/drawer/Drawer.vue new file mode 100644 index 0000000..05624d0 --- /dev/null +++ b/src/components/ui/drawer/Drawer.vue @@ -0,0 +1,19 @@ + + + + + + + diff --git a/src/components/ui/drawer/DrawerContent.vue b/src/components/ui/drawer/DrawerContent.vue new file mode 100644 index 0000000..6be17d0 --- /dev/null +++ b/src/components/ui/drawer/DrawerContent.vue @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/src/components/ui/drawer/DrawerDescription.vue b/src/components/ui/drawer/DrawerDescription.vue new file mode 100644 index 0000000..2a446c1 --- /dev/null +++ b/src/components/ui/drawer/DrawerDescription.vue @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/components/ui/drawer/DrawerFooter.vue b/src/components/ui/drawer/DrawerFooter.vue new file mode 100644 index 0000000..1eb3527 --- /dev/null +++ b/src/components/ui/drawer/DrawerFooter.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/drawer/DrawerHeader.vue b/src/components/ui/drawer/DrawerHeader.vue new file mode 100644 index 0000000..ecef7a6 --- /dev/null +++ b/src/components/ui/drawer/DrawerHeader.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/drawer/DrawerOverlay.vue b/src/components/ui/drawer/DrawerOverlay.vue new file mode 100644 index 0000000..c182463 --- /dev/null +++ b/src/components/ui/drawer/DrawerOverlay.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/components/ui/drawer/DrawerTitle.vue b/src/components/ui/drawer/DrawerTitle.vue new file mode 100644 index 0000000..f8d7704 --- /dev/null +++ b/src/components/ui/drawer/DrawerTitle.vue @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/components/ui/drawer/index.ts b/src/components/ui/drawer/index.ts new file mode 100644 index 0000000..b02bd3a --- /dev/null +++ b/src/components/ui/drawer/index.ts @@ -0,0 +1,8 @@ +export { DrawerPortal, DrawerTrigger, DrawerClose } from 'vaul-vue' +export { default as Drawer } from './Drawer.vue' +export { default as DrawerOverlay } from './DrawerOverlay.vue' +export { default as DrawerContent } from './DrawerContent.vue' +export { default as DrawerHeader } from './DrawerHeader.vue' +export { default as DrawerFooter } from './DrawerFooter.vue' +export { default as DrawerTitle } from './DrawerTitle.vue' +export { default as DrawerDescription } from './DrawerDescription.vue' diff --git a/src/components/ui/form/FormControl.vue b/src/components/ui/form/FormControl.vue new file mode 100644 index 0000000..8459cab --- /dev/null +++ b/src/components/ui/form/FormControl.vue @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/components/ui/form/FormDescription.vue b/src/components/ui/form/FormDescription.vue new file mode 100644 index 0000000..6085f76 --- /dev/null +++ b/src/components/ui/form/FormDescription.vue @@ -0,0 +1,20 @@ + + + + + + + diff --git a/src/components/ui/form/FormItem.vue b/src/components/ui/form/FormItem.vue new file mode 100644 index 0000000..ad120be --- /dev/null +++ b/src/components/ui/form/FormItem.vue @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/src/components/ui/form/FormLabel.vue b/src/components/ui/form/FormLabel.vue new file mode 100644 index 0000000..73cf45b --- /dev/null +++ b/src/components/ui/form/FormLabel.vue @@ -0,0 +1,23 @@ + + + + + + + diff --git a/src/components/ui/form/FormMessage.vue b/src/components/ui/form/FormMessage.vue new file mode 100644 index 0000000..308755e --- /dev/null +++ b/src/components/ui/form/FormMessage.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/src/components/ui/form/index.ts b/src/components/ui/form/index.ts new file mode 100644 index 0000000..30a30a6 --- /dev/null +++ b/src/components/ui/form/index.ts @@ -0,0 +1,6 @@ +export { Form, Field as FormField } from 'vee-validate' +export { default as FormItem } from './FormItem.vue' +export { default as FormLabel } from './FormLabel.vue' +export { default as FormControl } from './FormControl.vue' +export { default as FormMessage } from './FormMessage.vue' +export { default as FormDescription } from './FormDescription.vue' diff --git a/src/components/ui/form/useFormField.ts b/src/components/ui/form/useFormField.ts new file mode 100644 index 0000000..73eeee3 --- /dev/null +++ b/src/components/ui/form/useFormField.ts @@ -0,0 +1,30 @@ +import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate' +import { inject } from 'vue' +import { FORM_ITEM_INJECTION_KEY } from './FormItem.vue' + +export function useFormField() { + const fieldContext = inject(FieldContextKey) + const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY) + + const fieldState = { + valid: useIsFieldValid(), + isDirty: useIsFieldDirty(), + isTouched: useIsFieldTouched(), + error: useFieldError(), + } + + if (!fieldContext) + throw new Error('useFormField should be used within ') + + const { name } = fieldContext + const id = fieldItemContext + + return { + id, + name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + } +} diff --git a/src/components/ui/input/Input.vue b/src/components/ui/input/Input.vue new file mode 100644 index 0000000..39c9cee --- /dev/null +++ b/src/components/ui/input/Input.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/ui/input/index.ts b/src/components/ui/input/index.ts new file mode 100644 index 0000000..a691dd6 --- /dev/null +++ b/src/components/ui/input/index.ts @@ -0,0 +1 @@ +export { default as Input } from './Input.vue' diff --git a/src/components/ui/label/Label.vue b/src/components/ui/label/Label.vue new file mode 100644 index 0000000..8fba8db --- /dev/null +++ b/src/components/ui/label/Label.vue @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/components/ui/label/index.ts b/src/components/ui/label/index.ts new file mode 100644 index 0000000..572c2f0 --- /dev/null +++ b/src/components/ui/label/index.ts @@ -0,0 +1 @@ +export { default as Label } from './Label.vue' diff --git a/src/components/ui/table/Table.vue b/src/components/ui/table/Table.vue new file mode 100644 index 0000000..a423891 --- /dev/null +++ b/src/components/ui/table/Table.vue @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/components/ui/table/TableBody.vue b/src/components/ui/table/TableBody.vue new file mode 100644 index 0000000..ab7a937 --- /dev/null +++ b/src/components/ui/table/TableBody.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/TableCaption.vue b/src/components/ui/table/TableCaption.vue new file mode 100644 index 0000000..3904c56 --- /dev/null +++ b/src/components/ui/table/TableCaption.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/TableCell.vue b/src/components/ui/table/TableCell.vue new file mode 100644 index 0000000..4a4da40 --- /dev/null +++ b/src/components/ui/table/TableCell.vue @@ -0,0 +1,21 @@ + + + + + + + diff --git a/src/components/ui/table/TableEmpty.vue b/src/components/ui/table/TableEmpty.vue new file mode 100644 index 0000000..43fe7e0 --- /dev/null +++ b/src/components/ui/table/TableEmpty.vue @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/src/components/ui/table/TableFooter.vue b/src/components/ui/table/TableFooter.vue new file mode 100644 index 0000000..693a438 --- /dev/null +++ b/src/components/ui/table/TableFooter.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/TableHead.vue b/src/components/ui/table/TableHead.vue new file mode 100644 index 0000000..e882b60 --- /dev/null +++ b/src/components/ui/table/TableHead.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/TableHeader.vue b/src/components/ui/table/TableHeader.vue new file mode 100644 index 0000000..220352f --- /dev/null +++ b/src/components/ui/table/TableHeader.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/TableRow.vue b/src/components/ui/table/TableRow.vue new file mode 100644 index 0000000..5b9e874 --- /dev/null +++ b/src/components/ui/table/TableRow.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/ui/table/index.ts b/src/components/ui/table/index.ts new file mode 100644 index 0000000..44582d3 --- /dev/null +++ b/src/components/ui/table/index.ts @@ -0,0 +1,8 @@ +export { default as Table } from './Table.vue' +export { default as TableBody } from './TableBody.vue' +export { default as TableCell } from './TableCell.vue' +export { default as TableHead } from './TableHead.vue' +export { default as TableHeader } from './TableHeader.vue' +export { default as TableRow } from './TableRow.vue' +export { default as TableCaption } from './TableCaption.vue' +export { default as TableEmpty } from './TableEmpty.vue' diff --git a/src/components/ui/textarea/Textarea.vue b/src/components/ui/textarea/Textarea.vue new file mode 100644 index 0000000..b72cb7e --- /dev/null +++ b/src/components/ui/textarea/Textarea.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/src/components/ui/textarea/index.ts b/src/components/ui/textarea/index.ts new file mode 100644 index 0000000..6a7ab2a --- /dev/null +++ b/src/components/ui/textarea/index.ts @@ -0,0 +1 @@ +export { default as Textarea } from './Textarea.vue' diff --git a/src/data/entries.ts b/src/data/entries.ts index e69de29..fbfa9a0 100644 --- a/src/data/entries.ts +++ b/src/data/entries.ts @@ -0,0 +1,35 @@ +import moment, { Moment } from "moment"; +import { Ref, ref } from "vue"; + +const localStorageKey = 'entries' + +export const entries: Ref = ref(parseFromPossibleString(localStorage.getItem(localStorageKey))) + +export interface Entry { + name: string + text: string | undefined + last_reset: Moment +} + +export function parseFromPossibleString(input: string | null): Entry[] { + if (input === null) { + return [] + } + + const entries: Entry[] = [] + const rawObjects: any[] = JSON.parse(input) + + for (const rawObject of rawObjects) { + const { name, text, last_reset } = rawObject + + if (name && text && last_reset) { + entries.push({ name, text, last_reset: moment(last_reset) }) + } + } + + return entries +} + +export function save() { + localStorage.setItem(localStorageKey, JSON.stringify(entries.value)) +}
+ +