diff --git a/vue-frontend/.env b/vue-frontend/.env
new file mode 100644
index 0000000..dd97135
--- /dev/null
+++ b/vue-frontend/.env
@@ -0,0 +1 @@
+BASE_URL=https//5173.tzgyn.com
diff --git a/vue-frontend/.gitignore b/vue-frontend/.gitignore
new file mode 100644
index 0000000..38adffa
--- /dev/null
+++ b/vue-frontend/.gitignore
@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/vue-frontend/.prettierrc.yaml b/vue-frontend/.prettierrc.yaml
new file mode 100644
index 0000000..1439e2e
--- /dev/null
+++ b/vue-frontend/.prettierrc.yaml
@@ -0,0 +1,16 @@
+---
+printWidth: 80
+tabWidth: 4
+useTabs: true
+semi: false
+singleQuote: true
+quoteProps: consistent
+jsxSingleQuote: true
+trailingComma: es5
+bracketSpacing: true
+bracketSameLine: true
+arrowParens: always
+htmlWhitespaceSensitivity: strict
+vueIndentScriptAndStyle: false
+singleAttributePerLine: true
+plugins: [prettier-plugin-tailwindcss]
diff --git a/vue-frontend/.vscode/extensions.json b/vue-frontend/.vscode/extensions.json
new file mode 100644
index 0000000..c0a6e5a
--- /dev/null
+++ b/vue-frontend/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
+}
diff --git a/vue-frontend/README.md b/vue-frontend/README.md
new file mode 100644
index 0000000..e1cff9b
--- /dev/null
+++ b/vue-frontend/README.md
@@ -0,0 +1,40 @@
+# vue-frontend
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
+
+If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
+
+1. Disable the built-in TypeScript Extension
+ 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
+ 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
+2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+npm run build
+```
diff --git a/vue-frontend/bun.lockb b/vue-frontend/bun.lockb
new file mode 100755
index 0000000..f18d3ea
Binary files /dev/null and b/vue-frontend/bun.lockb differ
diff --git a/vue-frontend/components.json b/vue-frontend/components.json
new file mode 100644
index 0000000..59524eb
--- /dev/null
+++ b/vue-frontend/components.json
@@ -0,0 +1,15 @@
+{
+ "style": "default",
+ "typescript": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "src/assets/index.css",
+ "baseColor": "slate",
+ "cssVariables": true
+ },
+ "framework": "vite",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
\ No newline at end of file
diff --git a/vue-frontend/env.d.ts b/vue-frontend/env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/vue-frontend/env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/vue-frontend/index.html b/vue-frontend/index.html
new file mode 100644
index 0000000..a888544
--- /dev/null
+++ b/vue-frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite App
+
+
+
+
+
+
diff --git a/vue-frontend/package.json b/vue-frontend/package.json
new file mode 100644
index 0000000..183d8e2
--- /dev/null
+++ b/vue-frontend/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "vue-frontend",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "run-p type-check \"build-only {@}\" --",
+ "preview": "vite preview",
+ "build-only": "vite build",
+ "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false"
+ },
+ "dependencies": {
+ "@vueuse/core": "^10.5.0",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.0.0",
+ "lucide-vue-next": "^0.288.0",
+ "radix-vue": "^0.4.1",
+ "tailwind-merge": "^1.14.0",
+ "tailwindcss-animate": "^1.0.7",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.5"
+ },
+ "devDependencies": {
+ "@tsconfig/node18": "^18.2.2",
+ "@types/node": "^18.18.5",
+ "@vitejs/plugin-vue": "^4.4.0",
+ "@vue/tsconfig": "^0.4.0",
+ "autoprefixer": "^10.4.16",
+ "npm-run-all2": "^6.1.1",
+ "postcss": "^8.4.31",
+ "prettier": "^3.0.3",
+ "prettier-plugin-tailwindcss": "^0.5.6",
+ "tailwindcss": "^3.3.3",
+ "typescript": "~5.2.0",
+ "vite": "^4.4.11",
+ "vue-tsc": "^1.8.19"
+ }
+}
diff --git a/vue-frontend/postcss.config.js b/vue-frontend/postcss.config.js
new file mode 100644
index 0000000..33ad091
--- /dev/null
+++ b/vue-frontend/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/vue-frontend/public/favicon.ico b/vue-frontend/public/favicon.ico
new file mode 100644
index 0000000..df36fcf
Binary files /dev/null and b/vue-frontend/public/favicon.ico differ
diff --git a/vue-frontend/src/App.vue b/vue-frontend/src/App.vue
new file mode 100644
index 0000000..e9f44f1
--- /dev/null
+++ b/vue-frontend/src/App.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/vue-frontend/src/assets/index.css b/vue-frontend/src/assets/index.css
new file mode 100644
index 0000000..865834e
--- /dev/null
+++ b/vue-frontend/src/assets/index.css
@@ -0,0 +1,80 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
+
+#app {
+ @apply h-screen w-screen;
+}
diff --git a/vue-frontend/src/assets/logo.svg b/vue-frontend/src/assets/logo.svg
new file mode 100644
index 0000000..7565660
--- /dev/null
+++ b/vue-frontend/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/vue-frontend/src/components/Navbar.vue b/vue-frontend/src/components/Navbar.vue
new file mode 100644
index 0000000..1b27eb8
--- /dev/null
+++ b/vue-frontend/src/components/Navbar.vue
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/vue-frontend/src/components/ToggleThemeButton.vue b/vue-frontend/src/components/ToggleThemeButton.vue
new file mode 100644
index 0000000..c2054a8
--- /dev/null
+++ b/vue-frontend/src/components/ToggleThemeButton.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/vue-frontend/src/components/UserLoginForm.vue b/vue-frontend/src/components/UserLoginForm.vue
new file mode 100644
index 0000000..e93a791
--- /dev/null
+++ b/vue-frontend/src/components/UserLoginForm.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-frontend/src/components/UserSignUpForm.vue b/vue-frontend/src/components/UserSignUpForm.vue
new file mode 100644
index 0000000..bde2cd6
--- /dev/null
+++ b/vue-frontend/src/components/UserSignUpForm.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue-frontend/src/components/ui/button/Button.vue b/vue-frontend/src/components/ui/button/Button.vue
new file mode 100644
index 0000000..d721b1a
--- /dev/null
+++ b/vue-frontend/src/components/ui/button/Button.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
diff --git a/vue-frontend/src/components/ui/button/index.ts b/vue-frontend/src/components/ui/button/index.ts
new file mode 100644
index 0000000..4211057
--- /dev/null
+++ b/vue-frontend/src/components/ui/button/index.ts
@@ -0,0 +1,33 @@
+import { cva } from 'class-variance-authority'
+
+export { default as Button } from './Button.vue'
+
+export const buttonVariants = cva(
+ 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-primary-foreground text-primary hover:bg-primary-foreground/90',
+ destructive:
+ 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+ outline:
+ 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
+ secondary:
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ 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',
+ },
+ }
+)
diff --git a/vue-frontend/src/components/ui/input/Input.vue b/vue-frontend/src/components/ui/input/Input.vue
new file mode 100644
index 0000000..66ece7a
--- /dev/null
+++ b/vue-frontend/src/components/ui/input/Input.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
diff --git a/vue-frontend/src/components/ui/input/index.ts b/vue-frontend/src/components/ui/input/index.ts
new file mode 100644
index 0000000..a691dd6
--- /dev/null
+++ b/vue-frontend/src/components/ui/input/index.ts
@@ -0,0 +1 @@
+export { default as Input } from './Input.vue'
diff --git a/vue-frontend/src/components/ui/label/Label.vue b/vue-frontend/src/components/ui/label/Label.vue
new file mode 100644
index 0000000..e44d139
--- /dev/null
+++ b/vue-frontend/src/components/ui/label/Label.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/vue-frontend/src/components/ui/label/index.ts b/vue-frontend/src/components/ui/label/index.ts
new file mode 100644
index 0000000..572c2f0
--- /dev/null
+++ b/vue-frontend/src/components/ui/label/index.ts
@@ -0,0 +1 @@
+export { default as Label } from './Label.vue'
diff --git a/vue-frontend/src/lib/auth.ts b/vue-frontend/src/lib/auth.ts
new file mode 100644
index 0000000..b6c2799
--- /dev/null
+++ b/vue-frontend/src/lib/auth.ts
@@ -0,0 +1,3 @@
+export const isAuthenticated = async () => {
+ return false
+}
diff --git a/vue-frontend/src/lib/utils.ts b/vue-frontend/src/lib/utils.ts
new file mode 100644
index 0000000..24470b3
--- /dev/null
+++ b/vue-frontend/src/lib/utils.ts
@@ -0,0 +1,7 @@
+import { type ClassValue, clsx } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+import { camelize, getCurrentInstance, toHandlerKey } from 'vue'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/vue-frontend/src/main.ts b/vue-frontend/src/main.ts
new file mode 100644
index 0000000..72d93d8
--- /dev/null
+++ b/vue-frontend/src/main.ts
@@ -0,0 +1,11 @@
+import "./assets/index.css";
+
+import { createApp } from "vue";
+import App from "./App.vue";
+import router from "./router";
+
+const app = createApp(App);
+
+app.use(router);
+
+app.mount("#app");
diff --git a/vue-frontend/src/router/index.ts b/vue-frontend/src/router/index.ts
new file mode 100644
index 0000000..a5794bf
--- /dev/null
+++ b/vue-frontend/src/router/index.ts
@@ -0,0 +1,37 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import { isAuthenticated } from '@/lib/auth'
+import HomeView from '@/views/HomeView.vue'
+import LoginPage from '@/views/LoginPage.vue'
+import SignUpPage from '@/views/SignUpPage.vue'
+
+const router = createRouter({
+ history: createWebHistory(import.meta.env.BASE_URL),
+ routes: [
+ {
+ path: '/',
+ component: HomeView,
+ },
+ {
+ path: '/login',
+ name: 'login',
+ component: LoginPage,
+ },
+ {
+ path: '/signup',
+ name: 'signup',
+ component: SignUpPage,
+ },
+ ],
+})
+
+router.beforeEach(async (to) => {
+ const isLoggedIn = await isAuthenticated()
+ if (isLoggedIn && (to.name === 'login' || to.name === 'signup')) {
+ return { name: 'home' }
+ }
+ if (!isLoggedIn && to.name !== 'login' && to.name !== 'signup') {
+ return { name: 'login' }
+ }
+})
+
+export default router
diff --git a/vue-frontend/src/views/HomeView.vue b/vue-frontend/src/views/HomeView.vue
new file mode 100644
index 0000000..0a45c22
--- /dev/null
+++ b/vue-frontend/src/views/HomeView.vue
@@ -0,0 +1,3 @@
+
+ Test
+
diff --git a/vue-frontend/src/views/LoginPage.vue b/vue-frontend/src/views/LoginPage.vue
new file mode 100644
index 0000000..cd18c1e
--- /dev/null
+++ b/vue-frontend/src/views/LoginPage.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Shortener
+
+
+
+
+
+
+
+ Welcome Back
+
+
+ Sign in to your account
+
+
+
+
+ Don't have an account?
+
+ Sign Up Now
+
+
+
+
+
+
+
diff --git a/vue-frontend/src/views/SignUpPage.vue b/vue-frontend/src/views/SignUpPage.vue
new file mode 100644
index 0000000..f759724
--- /dev/null
+++ b/vue-frontend/src/views/SignUpPage.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Shortener
+
+
+
+
+
+
+
+ Get Started
+
+
+ Create a new account
+
+
+
+
+ Have an account?
+
+ Sign In Now
+
+
+
+
+
+
+
diff --git a/vue-frontend/tailwind.config.js b/vue-frontend/tailwind.config.js
new file mode 100644
index 0000000..aedb250
--- /dev/null
+++ b/vue-frontend/tailwind.config.js
@@ -0,0 +1,79 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: ['class'],
+
+ content: [
+ './pages/**/*.{ts,tsx,vue}',
+ './components/**/*.{ts,tsx,vue}',
+ './app/**/*.{ts,tsx,vue}',
+ './src/**/*.{ts,tsx,vue}',
+ './index.html',
+ ],
+
+ theme: {
+ container: {
+ center: true,
+ padding: '2rem',
+ screens: {
+ '2xl': '1400px',
+ },
+ },
+ extend: {
+ colors: {
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))',
+ },
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)',
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: { height: 0 },
+ to: { height: 'var(--radix-accordion-content-height)' },
+ },
+ 'accordion-up': {
+ from: { height: 'var(--radix-accordion-content-height)' },
+ to: { height: 0 },
+ },
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ },
+ },
+ },
+ plugins: [require('tailwindcss-animate')],
+}
diff --git a/vue-frontend/tsconfig.app.json b/vue-frontend/tsconfig.app.json
new file mode 100644
index 0000000..3e5b621
--- /dev/null
+++ b/vue-frontend/tsconfig.app.json
@@ -0,0 +1,12 @@
+{
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+ "exclude": ["src/**/__tests__/*"],
+ "compilerOptions": {
+ "composite": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/vue-frontend/tsconfig.json b/vue-frontend/tsconfig.json
new file mode 100644
index 0000000..66b5e57
--- /dev/null
+++ b/vue-frontend/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "files": [],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ },
+ {
+ "path": "./tsconfig.app.json"
+ }
+ ]
+}
diff --git a/vue-frontend/tsconfig.node.json b/vue-frontend/tsconfig.node.json
new file mode 100644
index 0000000..dee96be
--- /dev/null
+++ b/vue-frontend/tsconfig.node.json
@@ -0,0 +1,16 @@
+{
+ "extends": "@tsconfig/node18/tsconfig.json",
+ "include": [
+ "vite.config.*",
+ "vitest.config.*",
+ "cypress.config.*",
+ "nightwatch.conf.*",
+ "playwright.config.*"
+ ],
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "types": ["node"]
+ }
+}
diff --git a/vue-frontend/vite.config.ts b/vue-frontend/vite.config.ts
new file mode 100644
index 0000000..5c45e1d
--- /dev/null
+++ b/vue-frontend/vite.config.ts
@@ -0,0 +1,16 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [
+ vue(),
+ ],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ }
+})