diff --git a/frontend/components/ui/button/Button.vue b/frontend/components/ui/button/Button.vue
new file mode 100644
index 0000000..1094aa4
--- /dev/null
+++ b/frontend/components/ui/button/Button.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/button/index.ts b/frontend/components/ui/button/index.ts
new file mode 100644
index 0000000..c4cd814
--- /dev/null
+++ b/frontend/components/ui/button/index.ts
@@ -0,0 +1,37 @@
+import { type VariantProps, cva } from 'class-variance-authority';
+
+export { default as Button } from './Button.vue';
+
+export const buttonVariants = cva(
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-heading font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-slate-300',
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-primaryColor text-white shadow hover:bg-primaryColor/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90',
+ destructive:
+ 'bg-red-500 text-slate-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90',
+ outline:
+ 'border border-slate-200 bg-white shadow-sm hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50',
+ secondary:
+ 'bg-slate-100 text-slate-900 shadow-sm hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80',
+ ghost:
+ 'hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50',
+ link: 'text-slate-900 underline-offset-4 hover:underline dark:text-slate-50'
+ },
+ size: {
+ default: 'h-9 px-4 py-2',
+ xs: 'h-7 rounded-md px-2 text-xs',
+ sm: 'h-8 rounded-lg px-3 text-xs',
+ lg: 'h-10 rounded-lg px-8',
+ icon: 'h-9 w-9'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ }
+);
+
+export type ButtonVariants = VariantProps;
diff --git a/frontend/components/ui/card/Card.vue b/frontend/components/ui/card/Card.vue
new file mode 100644
index 0000000..c20abf1
--- /dev/null
+++ b/frontend/components/ui/card/Card.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/CardContent.vue b/frontend/components/ui/card/CardContent.vue
new file mode 100644
index 0000000..8120c38
--- /dev/null
+++ b/frontend/components/ui/card/CardContent.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/CardDescription.vue b/frontend/components/ui/card/CardDescription.vue
new file mode 100644
index 0000000..62ee7eb
--- /dev/null
+++ b/frontend/components/ui/card/CardDescription.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/CardFooter.vue b/frontend/components/ui/card/CardFooter.vue
new file mode 100644
index 0000000..2ccd68b
--- /dev/null
+++ b/frontend/components/ui/card/CardFooter.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/CardHeader.vue b/frontend/components/ui/card/CardHeader.vue
new file mode 100644
index 0000000..b2eaf0c
--- /dev/null
+++ b/frontend/components/ui/card/CardHeader.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/CardTitle.vue b/frontend/components/ui/card/CardTitle.vue
new file mode 100644
index 0000000..1a9bb61
--- /dev/null
+++ b/frontend/components/ui/card/CardTitle.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/card/index.ts b/frontend/components/ui/card/index.ts
new file mode 100644
index 0000000..872bc81
--- /dev/null
+++ b/frontend/components/ui/card/index.ts
@@ -0,0 +1,6 @@
+export { default as Card } from './Card.vue';
+export { default as CardHeader } from './CardHeader.vue';
+export { default as CardTitle } from './CardTitle.vue';
+export { default as CardDescription } from './CardDescription.vue';
+export { default as CardContent } from './CardContent.vue';
+export { default as CardFooter } from './CardFooter.vue';
diff --git a/frontend/components/ui/form/FormControl.vue b/frontend/components/ui/form/FormControl.vue
new file mode 100644
index 0000000..f8ad425
--- /dev/null
+++ b/frontend/components/ui/form/FormControl.vue
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/form/FormDescription.vue b/frontend/components/ui/form/FormDescription.vue
new file mode 100644
index 0000000..d7168df
--- /dev/null
+++ b/frontend/components/ui/form/FormDescription.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/form/FormItem.vue b/frontend/components/ui/form/FormItem.vue
new file mode 100644
index 0000000..4c2cdcd
--- /dev/null
+++ b/frontend/components/ui/form/FormItem.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/ui/form/FormLabel.vue b/frontend/components/ui/form/FormLabel.vue
new file mode 100644
index 0000000..15fd6df
--- /dev/null
+++ b/frontend/components/ui/form/FormLabel.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/frontend/components/ui/form/FormMessage.vue b/frontend/components/ui/form/FormMessage.vue
new file mode 100644
index 0000000..9b0f484
--- /dev/null
+++ b/frontend/components/ui/form/FormMessage.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/frontend/components/ui/form/index.ts b/frontend/components/ui/form/index.ts
new file mode 100644
index 0000000..940e5d4
--- /dev/null
+++ b/frontend/components/ui/form/index.ts
@@ -0,0 +1,11 @@
+export {
+ Form,
+ Field as FormField,
+ FieldArray as FormFieldArray
+} 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';
+export { FORM_ITEM_INJECTION_KEY } from './injectionKeys';
diff --git a/frontend/components/ui/form/injectionKeys.ts b/frontend/components/ui/form/injectionKeys.ts
new file mode 100644
index 0000000..2338043
--- /dev/null
+++ b/frontend/components/ui/form/injectionKeys.ts
@@ -0,0 +1,3 @@
+import type { InjectionKey } from 'vue';
+
+export const FORM_ITEM_INJECTION_KEY = Symbol() as InjectionKey;
diff --git a/frontend/components/ui/form/useFormField.ts b/frontend/components/ui/form/useFormField.ts
new file mode 100644
index 0000000..ac87fbe
--- /dev/null
+++ b/frontend/components/ui/form/useFormField.ts
@@ -0,0 +1,36 @@
+import {
+ FieldContextKey,
+ useFieldError,
+ useIsFieldDirty,
+ useIsFieldTouched,
+ useIsFieldValid
+} from 'vee-validate';
+import { inject } from 'vue';
+import { FORM_ITEM_INJECTION_KEY } from './injectionKeys';
+
+export function useFormField() {
+ const fieldContext = inject(FieldContextKey);
+ const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY);
+
+ if (!fieldContext)
+ throw new Error('useFormField should be used within ');
+
+ const { name } = fieldContext;
+ const id = fieldItemContext;
+
+ const fieldState = {
+ valid: useIsFieldValid(name),
+ isDirty: useIsFieldDirty(name),
+ isTouched: useIsFieldTouched(name),
+ error: useFieldError(name)
+ };
+
+ return {
+ id,
+ name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState
+ };
+}
diff --git a/frontend/components/ui/input/Input.vue b/frontend/components/ui/input/Input.vue
new file mode 100644
index 0000000..e38f950
--- /dev/null
+++ b/frontend/components/ui/input/Input.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
diff --git a/frontend/components/ui/input/index.ts b/frontend/components/ui/input/index.ts
new file mode 100644
index 0000000..c5248c5
--- /dev/null
+++ b/frontend/components/ui/input/index.ts
@@ -0,0 +1 @@
+export { default as Input } from './Input.vue';
diff --git a/frontend/components/ui/label/Label.vue b/frontend/components/ui/label/Label.vue
new file mode 100644
index 0000000..f04c37a
--- /dev/null
+++ b/frontend/components/ui/label/Label.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/frontend/components/ui/label/index.ts b/frontend/components/ui/label/index.ts
new file mode 100644
index 0000000..c98e59f
--- /dev/null
+++ b/frontend/components/ui/label/index.ts
@@ -0,0 +1 @@
+export { default as Label } from './Label.vue';