feat(frontend): added pinia, i18n and started login

This commit is contained in:
Marco Cavalli 2025-02-21 14:39:57 +01:00
parent 64c11393c4
commit 0392c0c7cf
14 changed files with 455 additions and 20 deletions

223
package-lock.json generated
View file

@ -6,6 +6,9 @@
"": {
"dependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"pinia": "^3.0.1",
"vue-i18n": "^10.0.5",
"vue-router": "^4.5.0",
"vuetify": "^3.7.12"
},
"devDependencies": {
@ -450,6 +453,50 @@
"node": ">=12"
}
},
"node_modules/@intlify/core-base": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.5.tgz",
"integrity": "sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==",
"license": "MIT",
"dependencies": {
"@intlify/message-compiler": "10.0.5",
"@intlify/shared": "10.0.5"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.5.tgz",
"integrity": "sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==",
"license": "MIT",
"dependencies": {
"@intlify/shared": "10.0.5",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.5.tgz",
"integrity": "sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==",
"license": "MIT",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -892,6 +939,36 @@
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/devtools-kit": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz",
"integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.2",
"birpc": "^0.2.19",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.1"
}
},
"node_modules/@vue/devtools-shared": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz",
"integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.13",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
@ -1088,6 +1165,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/birpc": {
"version": "0.2.19",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
"integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -1246,6 +1332,21 @@
"node": ">= 6"
}
},
"node_modules/copy-anything": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
"license": "MIT",
"dependencies": {
"is-what": "^4.1.8"
},
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -1567,6 +1668,12 @@
"node": ">= 0.4"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -1631,6 +1738,18 @@
"node": ">=0.12.0"
}
},
"node_modules/is-what": {
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
"license": "MIT",
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -1794,6 +1913,12 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -1896,6 +2021,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -1923,6 +2054,36 @@
"node": ">=0.10.0"
}
},
"node_modules/pinia": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.1.tgz",
"integrity": "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.2"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.4.4",
"vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/@vue/devtools-api": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.2.tgz",
"integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.2"
}
},
"node_modules/pirates": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
@ -2155,6 +2316,12 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rollup": {
"version": "4.34.8",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
@ -2258,6 +2425,15 @@
"node": ">=0.10.0"
}
},
"node_modules/speakingurl": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@ -2376,6 +2552,18 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/superjson": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
"license": "MIT",
"dependencies": {
"copy-anything": "^3.0.2"
},
"engines": {
"node": ">=16"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@ -2606,6 +2794,41 @@
}
}
},
"node_modules/vue-i18n": {
"version": "10.0.5",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.5.tgz",
"integrity": "sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==",
"license": "MIT",
"dependencies": {
"@intlify/core-base": "10.0.5",
"@intlify/shared": "10.0.5",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-router": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz",
"integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/vuetify": {
"version": "3.7.12",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.12.tgz",

View file

@ -18,6 +18,9 @@
},
"dependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"pinia": "^3.0.1",
"vue-i18n": "^10.0.5",
"vue-router": "^4.5.0",
"vuetify": "^3.7.12"
}
}

View file

@ -1,6 +1,21 @@
import './bootstrap';
import vuetify from "./vuetify";
import { createApp } from "vue";
import app from "./layouts/app.vue";
import router from "./helpers/router.ts";
import { vueI18n } from "@/locale/i18n.ts";
createApp(app).use(vuetify).mount("#app");
import { createApp } from "vue";
import { createPinia } from "pinia";
import { createI18n } from "vue-i18n";
import App from "@/layouts/App.vue";
import {components} from "vuetify/dist/vuetify";
const pinia = createPinia();
const i18n = createI18n(vueI18n);
const app = createApp(App);
console.log(i18n)
app.use(pinia)
.use(i18n)
.use(router)
.use(vuetify)
.mount("#app");

View file

@ -0,0 +1,37 @@
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "@/stores/auth.store.ts";
const routes = [
{
path: '/',
name: 'Dashboard',
component: () => import('../pages/Backoffice.vue'),
},
{
path: '/login',
name: 'Login',
component: () => import('../pages/Login.vue'),
},
];
const router = createRouter({
history: createWebHistory(),
routes
});
export default router;
/**
* Redirect to login page if unauthenticated
*/
router.beforeEach(async (to) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const auth = useAuthStore();
if (authRequired && !auth.userData) {
auth.returnUrl = to.fullPath;
return '/login';
}
});

View file

@ -0,0 +1,22 @@
<script setup lang="ts">
</script>
<template>
<v-container :fluid="true">
<router-view v-slot="{ Component, route }">
<div :key="route.name">
<Component :is="Component" />
</div>
</router-view>
</v-container>
</template>
<style scoped>
body {
height: 100vh;
}
.v-container {
padding: 2px;
}
</style>

View file

@ -1,15 +0,0 @@
<script setup lang="ts">
</script>
<template>
<v-container :fluid="true">
</v-container>
</template>
<style scoped>
.v-container {
padding: 2px;
}
</style>

View file

@ -0,0 +1,11 @@
import {loginIt} from "@/locale/it/login.ts";
export const vueI18n = {
locale: 'it',
fallbackLocale: 'it',
messages: {
it: {
login: loginIt
}
}
}

View file

@ -0,0 +1,9 @@
export const loginIt = {
title: 'Login',
subtitle: 'Subtitle',
username: 'Nome utente',
password: 'Password',
errors: {
required: 'Il campo è richiesto'
}
}

View file

@ -0,0 +1,34 @@
<script setup lang="ts">
import axios from 'axios'
import { ref } from 'vue'
const userData = ref(null)
const isLoading = ref(true)
const fetchUserData = async () => {
try {
// Replace http://127.0.0.1:8000 with Laravel domain
const response = await axios.get('http://127.0.0.1:9876/api/secured')
if (response.status === 200 && response.data.user) {
userData.value = response.data
}
} catch (error) {
// Handle errors appropriately (e.g., display an error message)
console.error(error)
}
isLoading.value = false
}
await fetchUserData()
</script>
<template>
<div>
<h2>HOME</h2>
<router-link to="/login"> Take me to login page </router-link>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,81 @@
<script setup lang="ts">
import {reactive} from 'vue';
import axios from "axios";
import {useAuthStore} from "@/stores/auth.store.ts";
const data = reactive({
'form': false,
'username': null,
'password': null,
loading: false,
});
const onSubmit = () => {
if (!data.form) return;
axios.post('/login', {
username: data.username,
password: data.password,
// csrf: document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}).then((response) => {
if (response.status === 200) {
const auth = useAuthStore();
auth.setUserData(response.data.user);
}
});
data.loading = true;
setTimeout(() => (data.loading = false), 2000);
}
const required = (v) => {
return !!v || $t('login.errors.required');
}
</script>
<template>
<v-container class="d-flex items-center justify-center">
<v-card class="mx-auto px-6 py-8" min-width="500">
<v-form
v-model="data.form"
@submit.prevent="onSubmit"
>
<v-card-item class="text-center">
<v-card-title>{{ $t('login.title') }}</v-card-title>
<v-card-subtitle>{{ $t('login.subtitle') }}</v-card-subtitle>
</v-card-item>
<v-card-text>
<v-text-field
v-model="data.username"
class="mb-2"
label="Username"
></v-text-field>
<v-text-field
v-model="data.password"
label="Password"
placeholder="Enter your password"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn
:disabled="!data.form"
:loading="loading"
color="success"
size="large"
type="submit"
variant="elevated"
@click="onSubmit"
block
>
Sign In
</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-container>
</template>
<style scoped>
.v-container {
height: 100vh;
}
</style>

View file

@ -0,0 +1,14 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAuthStore = defineStore('authStore', () => {
// A variable ref to store the user data
const userData = ref(null)
// A function acts as a setter to set the incoming user data
const setUserData = (newUserData) => {
userData.value = newUserData
}
return { userData, setUserData }
})

View file

@ -40,7 +40,7 @@ const vuetify = createVuetify({
components,
directives,
theme: {
defaultTheme: 'dark',
defaultTheme: 'light',
// themes: {
// customeTheme,
// },

View file

@ -20,7 +20,7 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::view('/', 'layouts.app');
Route::view('/{vue_capture?}', 'layouts.app')->where('vue_capture', '[\/\w\.-]*');
/**
* Create routes without create method

View file

@ -5,6 +5,7 @@ import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [
vue(),
i18n(),
laravel({
input: [
'resources/css/app.css',