feat: Initialize project with configuration files and basic structure
This commit is contained in:
1
.env.example
Normal file
1
.env.example
Normal file
@@ -0,0 +1 @@
|
||||
NUXT_PUBLIC_API_BASE_URL=http://localhost:3000
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
.nuxt/
|
||||
.output/
|
||||
.env
|
||||
dist/
|
||||
coverage/
|
||||
6
app.vue
Normal file
6
app.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<NuxtRouteAnnouncer />
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
6
middleware/auth.ts
Normal file
6
middleware/auth.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { useAuthStore } from '~/stores/auth';
|
||||
|
||||
export default defineNuxtRouteMiddleware(() => {
|
||||
const auth = useAuthStore();
|
||||
if (!auth.isAuthenticated) return navigateTo('/login');
|
||||
});
|
||||
9
nuxt.config.ts
Normal file
9
nuxt.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'],
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBaseUrl: '', // set via NUXT_PUBLIC_API_BASE_URL
|
||||
},
|
||||
},
|
||||
})
|
||||
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "nest-intranet",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:update": "vitest --update-snapshots"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.10.0",
|
||||
"nuxt": "^3.15.0",
|
||||
"pinia": "^2.3.0",
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/test-utils": "^3.15.0",
|
||||
"@nuxtjs/tailwindcss": "^6.13.0",
|
||||
"@vitest/coverage-v8": "^3.0.0",
|
||||
"typescript": "^5.7.0",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
9
pages/dashboard.vue
Normal file
9
pages/dashboard.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({ middleware: 'auth' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold">Dashboard</h1>
|
||||
</div>
|
||||
</template>
|
||||
46
pages/login.vue
Normal file
46
pages/login.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '~/stores/auth';
|
||||
|
||||
definePageMeta({ layout: false });
|
||||
|
||||
const auth = useAuthStore();
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
const error = ref('');
|
||||
|
||||
async function submit() {
|
||||
error.value = '';
|
||||
try {
|
||||
await auth.login({ email: email.value, password: password.value });
|
||||
await navigateTo('/dashboard');
|
||||
} catch {
|
||||
error.value = 'Invalid credentials.';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<form class="w-full max-w-sm space-y-4" @submit.prevent="submit">
|
||||
<h1 class="text-2xl font-bold text-center">CrowMate Intranet</h1>
|
||||
<p v-if="error" class="text-red-500 text-sm text-center">{{ error }}</p>
|
||||
<input
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
class="w-full border rounded px-3 py-2 text-sm"
|
||||
/>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
class="w-full border rounded px-3 py-2 text-sm"
|
||||
/>
|
||||
<button type="submit" class="w-full bg-black text-white rounded py-2 text-sm font-medium">
|
||||
Sign in
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
31
stores/auth.ts
Normal file
31
stores/auth.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
interface AuthUser {
|
||||
id: string;
|
||||
email: string;
|
||||
username: string;
|
||||
role: 'ADMIN' | 'DEV' | 'MEMBER';
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref<AuthUser | null>(null);
|
||||
const token = ref<string | null>(null);
|
||||
const isAuthenticated = computed(() => !!token.value);
|
||||
|
||||
async function login(credentials: { email: string; password: string }) {
|
||||
const config = useRuntimeConfig();
|
||||
const data = await $fetch<{ token: string; user: AuthUser }>(
|
||||
`${config.public.apiBaseUrl}/auth/login`,
|
||||
{ method: 'POST', body: credentials },
|
||||
);
|
||||
token.value = data.token;
|
||||
user.value = data.user;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
token.value = null;
|
||||
user.value = null;
|
||||
}
|
||||
|
||||
return { user, token, isAuthenticated, login, logout };
|
||||
});
|
||||
3
tsconfig.json
Normal file
3
tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nuxt/tsconfig.json"
|
||||
}
|
||||
7
vitest.config.ts
Normal file
7
vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineVitestConfig } from '@nuxt/test-utils/config';
|
||||
|
||||
export default defineVitestConfig({
|
||||
test: {
|
||||
environment: 'nuxt',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user