Skip to content

Commit

Permalink
feat: add login page
Browse files Browse the repository at this point in the history
  • Loading branch information
risv1 committed Apr 23, 2024
1 parent 258a789 commit bfbe3c7
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 4 deletions.
121 changes: 121 additions & 0 deletions components/Login.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<template>
<form
class="border-2 shadow-md hover:shadow-green-500 duration-200 ease-in-out border-green-500 rounded-lg bg-black bg-opacity-40 w-1/3 h-fit p-5 flex flex-col gap-7"
@submit="onSubmit"
>
<h1 class="text-green-500 text-3xl font-semibold self-center">Login</h1>
<div class="flex flex-col gap-2">
<label class="text-green-500 text-2xl font-normal">Email</label>
<Input
type="email"
placeholder="Email"
v-model="email"
class="text-lg p-2 rounded-md focus:outline-none"
/>
</div>
<div class="flex flex-col gap-2">
<label class="text-green-500 text-2xl font-normal">Password</label>
<Input
type="password"
placeholder="Password"
v-model="password"
class="text-lg p-2 rounded-md focus:outline-none"
/>
</div>
<Button
type="submit"
@click="onSubmit($event)"
class="bg-green-500 hover:bg-green-600 mt-auto"
>Submit
</Button>
<p
@click="switchLayer"
class="text-green-500 text-md font-medium hover:cursor-pointer self-center"
>
Don't have an account? Click to register!
</p>
</form>
</template>

<script setup>
import { useAuthStore } from "@/store/auth";
import { useToast } from "@/components/ui/toast/use-toast";
import { ToastAction } from "@/components/ui/toast";
import { ref } from "vue";
import { z, object } from "zod";
import { Input } from "@/components/ui/Input";
import { Button } from "@/components/ui/Button";
defineProps({
switchLayer: {
type: Function,
required: true,
},
});
const { user, setUser } = useAuthStore();
const { toast } = useToast();
const email = ref("");
const password = ref("");
const submitSchema = object({
email: z.string().email(),
password: z.string().min(8),
});
const onSubmit = async (event) => {
event.preventDefault();
try {
const validatedData = submitSchema.parse({
email: email.value,
password: password.value,
});
if (validatedData) {
const res = await fetch("http://localhost:3000/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email.value,
password: password.value,
}),
});
if (!res.ok) {
throw createError({
message: "Unable to submit form!",
statusCode: 400,
});
}
if(res.ok){
const data = await res.json();
setUser({
name: data.user.name,
email: data.user.email,
});
console.log("Form submitted successfully!", data);
toast({
title: "Success!",
description: "User logged in successfully!",
});
navigateTo("/");
}
} else {
throw createError({
message: "Form validation failed!",
statusCode: 400,
});
}
} catch (error) {
toast({
title: "Error",
description: "Unable to submit form!",
});
throw createError({
message: "Unable to submit form!",
statusCode: 400,
});
}
};
</script>
127 changes: 127 additions & 0 deletions components/Register.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<template>
<form
class="border-2 shadow-md hover:shadow-green-500 duration-200 ease-in-out border-green-500 rounded-lg bg-black bg-opacity-40 w-1/3 h-fit p-5 flex flex-col gap-5"
@submit="onSubmit"
>
<h1 class="text-green-500 text-3xl font-semibold self-center">Sign up</h1>
<div class="flex flex-col gap-2">
<label class="text-green-500 text-2xl font-normal">Name</label>
<Input
type="name"
placeholder="Name"
v-model="name"
class="text-lg p-2 rounded-md focus:outline-none"
/>
</div>
<div class="flex flex-col gap-2">
<label class="text-green-500 text-2xl font-normal">Email</label>
<Input
type="email"
placeholder="Email"
v-model="email"
class="text-lg p-2 rounded-md focus:outline-none"
/>
</div>
<div class="flex flex-col gap-2">
<label class="text-green-500 text-2xl font-normal">Password</label>
<Input
type="password"
placeholder="Password"
v-model="password"
class="text-lg p-2 rounded-md focus:outline-none"
/>
</div>
<Button
type="submit"
@click="onSubmit($event)"
class="bg-green-500 hover:bg-green-600 mt-auto"
>Submit
</Button>
<p
@click="switchLayer"
class="text-green-500 text-md font-medium hover:cursor-pointer self-center"
>
Already have an account? Click to login!
</p>
</form>
</template>

<script setup>
import { useToast } from "@/components/ui/toast/use-toast";
import { ToastAction } from "@/components/ui/toast";
import { h } from "vue";
import { ref } from "vue";
import { z, object } from "zod";
import { Input } from "@/components/ui/Input";
import { Button } from "@/components/ui/Button";
const { toast } = useToast();
defineProps({
switchLayer: {
type: Function,
requigreen: true,
},
});
const name = ref("");
const email = ref("");
const password = ref("");
const submitSchema = object({
name: z.string().min(3),
email: z.string().email(),
password: z.string().min(8),
});
const onSubmit = async (event) => {
event.preventDefault();
try {
const validatedData = submitSchema.parse({
name: name.value,
email: email.value,
password: password.value,
});
if (validatedData) {
const res = await fetch("http://localhost:3000/api/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: name.value,
email: email.value,
password: password.value,
}),
});
if (!res.ok) {
throw createError({
message: "Unable to submit form!",
statusCode: 400,
});
}
console.log("Form submitted successfully!", res.body);
toast({
title: "Success",
description: "Registegreen successfully!",
});
} else {
throw createError({
message: "Form validation failed!",
statusCode: 400,
});
}
} catch (error) {
toast({
title: "Error",
description: "Unable to submit form!",
});
console.log(error);
throw createError({
message: "Unable to submit form!",
statusCode: 400,
});
}
};
</script>

3 changes: 1 addition & 2 deletions components/ToastButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
</template>

<script setup>
import { useToast } from '@/components/ui/toast/use-toast'
import { ToastAction } from '@/components/ui/toast'
Expand All @@ -20,7 +19,7 @@ const props = defineProps({
},
description: {
type: String,
required: true
required: false
},
})
Expand Down
26 changes: 26 additions & 0 deletions components/ui/button/Button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Primitive, type PrimitiveProps } from 'radix-vue'
import { type ButtonVariants, buttonVariants } from '.'
import { cn } from '@/lib/utils'
interface Props extends PrimitiveProps {
variant?: ButtonVariants['variant']
size?: ButtonVariants['size']
class?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
as: 'button',
})
</script>

<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>
</template>
34 changes: 34 additions & 0 deletions components/ui/button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300',
{
variants: {
variant: {
default: 'bg-slate-900 text-slate-50 hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90',
destructive:
'bg-red-500 text-slate-50 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 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 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-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)

export type ButtonVariants = VariantProps<typeof buttonVariants>
24 changes: 24 additions & 0 deletions components/ui/input/Input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { useVModel } from '@vueuse/core'
import { cn } from '@/lib/utils'
const props = defineProps<{
defaultValue?: string | number
modelValue?: string | number
class?: HTMLAttributes['class']
}>()
const emits = defineEmits<{
(e: 'update:modelValue', payload: string | number): void
}>()
const modelValue = useVModel(props, 'modelValue', emits, {
passive: true,
defaultValue: props.defaultValue,
})
</script>

<template>
<input v-model="modelValue" :class="cn('flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300', props.class)">
</template>
1 change: 1 addition & 0 deletions components/ui/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Input } from './Input.vue'
2 changes: 1 addition & 1 deletion components/ui/toast/Toaster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { toasts } = useToast()
{{ toast.description }}
</ToastDescription>
</template>
<ToastClose />
<ToastClose class="text-white hover:text-white" />
</div>
<component class="hover:bg-white hover:text-black" :is="toast.action" />
</Toast>
Expand Down
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
"db:push": "drizzle-kit push:pg"
},
"dependencies": {
"@pinia/nuxt": "^0.5.1",
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.6",
"@types/uuid": "^9.0.8",
"@vee-validate/zod": "^4.12.6",
"@vueuse/core": "^10.9.0",
"bcrypt": "^5.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
Expand All @@ -23,6 +26,7 @@
"jsonwebtoken": "^9.0.2",
"lucide-vue-next": "^0.372.0",
"nuxt": "^3.11.2",
"pinia": "^2.1.7",
"postgres": "^3.4.4",
"radix-vue": "^1.7.1",
"tailwind-merge": "^2.3.0",
Expand All @@ -34,6 +38,9 @@
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.12.0",
"shadcn-nuxt": "^0.10.2"
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"shadcn-nuxt": "^0.10.2",
"tailwindcss": "^3.4.3"
}
}
Loading

0 comments on commit bfbe3c7

Please sign in to comment.