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