/ app / pages / login.vue
login.vue
  1  <template>
  2    <main>
  3      <div class="branding">
  4        <h1 class="title">
  5          <svg
  6            width="124"
  7            height="28"
  8            viewBox="0 0 124 28"
  9            fill="none"
 10            xmlns="http://www.w3.org/2000/svg">
 11            <defs>
 12              <clipPath id="clip_path_1">
 13                <rect width="124" height="28" />
 14              </clipPath>
 15            </defs>
 16            <path
 17              d="M29.32 -1.90735e-06L1.32 -1.90735e-06L1.32 5.6L18.12 5.6L1.32 28L29.32 28L29.32 22.4L12.5197 22.4L29.32 -1.90735e-06ZM60.44 5.6L60.44 -1.90735e-06L32.44 -1.90735e-06L32.44 5.6L43.64 5.6L43.64 22.4L32.44 22.4L32.44 28L60.44 28L60.44 22.4L49.24 22.4L49.24 5.6L60.44 5.6ZM92.04 5.6L92.04 -1.90735e-06L64.04 -1.90735e-06L64.04 5.6L75.24 5.6L75.24 22.4L64.04 22.4L64.04 28L92.04 28L92.04 22.4L80.84 22.4L80.84 5.6L92.04 5.6ZM94.72 -1.90735e-06L94.72 5.6L105.92 5.6L105.92 28L111.52 28L111.52 5.6L122.72 5.6L122.72 -1.90735e-06L94.72 -1.90735e-06Z"
 18              fill="#E6E6E6"
 19              clip-path="url(#clip_path_1)" />
 20          </svg>
 21        </h1>
 22        <p class="description">
 23          Please sign in to your account or
 24          <NuxtLink to="/register"><u>Register</u></NuxtLink>
 25        </p>
 26      </div>
 27      <form
 28        class="form"
 29        @submit.prevent="login"
 30        autocomplete="on"
 31        data-form-type="login">
 32        <UiInput
 33          v-model="email"
 34          placeholder="Email"
 35          type="text"
 36          :icon="LucideMail" />
 37        <UiInput
 38          v-model="password"
 39          placeholder="Password"
 40          type="password"
 41          :icon="LucideKeyRound" />
 42      </form>
 43      <div class="buttons">
 44        <UiButton text="Login" keyName="enter" @click="login" />
 45        <UiButton text="Login with Github" keyName="g" @click="githubAuth" />
 46      </div>
 47    </main>
 48  </template>
 49  
 50  <script setup lang="ts">
 51  import { LucideKeyRound, LucideMail } from "lucide-vue-next";
 52  import { Key } from "@waradu/keyboard";
 53  
 54  const error = ref("");
 55  const email = ref("");
 56  const password = ref("");
 57  const toast = useToast();
 58  const route = useRoute();
 59  
 60  onMounted(() => {
 61    if (route.query.error) {
 62      const errorMessages: Record<string, string> = {
 63        invalid_state: "Invalid authentication state, please try again",
 64        no_code: "No authorization code received",
 65        no_email: "No email address found in your GitHub account",
 66        github_auth_failed: "GitHub authentication failed",
 67      };
 68  
 69      const message =
 70        errorMessages[route.query.error as string] || "Authentication error";
 71      toast.error(message);
 72    }
 73  
 74    if (route.query.success) {
 75      const successMessages: Record<string, string> = {
 76        logout: "Logged out successfully",
 77      };
 78  
 79      const message = successMessages[route.query.success as string] || "Success";
 80      toast.success(message);
 81    }
 82  });
 83  
 84  useKeybind(
 85    [Key.G],
 86    async () => {
 87      await githubAuth();
 88    },
 89    { ignoreIfEditable: true }
 90  );
 91  
 92  useKeybind(
 93    [Key.Enter],
 94    async () => {
 95      await login();
 96    },
 97    { prevent: true }
 98  );
 99  
100  async function login() {
101    error.value = "";
102    try {
103      await $fetch("/api/auth/login", {
104        method: "POST",
105        body: {
106          email: email.value,
107          password: password.value,
108        },
109      });
110      await navigateTo("/");
111    } catch (e: any) {
112      error.value = e.data?.message || "Login failed";
113      toast.error(error.value);
114    }
115  }
116  
117  async function githubAuth() {
118    window.location.href = "/api/auth/github";
119  }
120  
121  useSeoMeta({
122    title: "Ziit - Coding Statistics",
123    description: "Track your coding time and productivity with Ziit",
124    ogTitle: "Ziit - Coding Statistics",
125    ogDescription: "Track your coding time and productivity with Ziit",
126    ogImage: "https://ziit.app/logo.webp",
127    ogUrl: "https://ziit.app/login",
128    ogSiteName: "Ziit",
129    twitterTitle: "Ziit - Coding Statistics",
130    twitterDescription: "Track your coding time and productivity with Ziit",
131    twitterImage: "https://ziit.app/logo.webp",
132    twitterCard: "summary",
133    twitterCreator: "@pandadev_",
134    twitterSite: "@pandadev_",
135    author: "PandaDEV",
136  });
137  
138  useHead({
139    htmlAttrs: { lang: "en" },
140    link: [
141      {
142        rel: "canonical",
143        href: "https://ziit.app/login",
144      },
145      {
146        rel: "icon",
147        type: "image/ico",
148        href: "/favicon.ico",
149      },
150    ],
151    script: [
152      {
153        type: "application/ld+json",
154        innerHTML: JSON.stringify({
155          "@context": "https://schema.org",
156          "@type": "WebPage",
157          name: "Login - Ziit",
158          url: "https://ziit.app/login",
159        }),
160      },
161    ],
162  });
163  </script>
164  
165  <style lang="scss">
166  @use "~~/styles/login.scss";
167  </style>