gestione profilo utente
This commit is contained in:
@@ -656,3 +656,78 @@ The availability check endpoint (backend) will reference:
|
||||
This implementation successfully adds a complete availability configuration system for facilities. Authorized users (ADMIN and INCARICATO) can manage both specific date closures and recurring day-of-week availability patterns. The solution is fully reactive, handles edge cases gracefully, and provides clear visual feedback throughout the process.
|
||||
|
||||
The feature follows Vue 3 Composition API best practices, integrates seamlessly with the existing JHipster application structure, and maintains consistency with the application's UI/UX patterns.
|
||||
|
||||
---
|
||||
|
||||
# Frontend Changes: User Profile Management Feature
|
||||
|
||||
## Date
|
||||
|
||||
2025-12-16
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the frontend changes made to implement the user profile management feature for the Smartbooking application. This feature allows registered users to complete and manage their UtenteApp profile information, which is essential for making reservations.
|
||||
|
||||
## Feature Summary
|
||||
|
||||
After registration, users can access a dedicated interface to input and modify their UtenteApp data. The interface consists of two sections:
|
||||
|
||||
1. **Personal Information** (required): nome, cognome, data di nascita, luogo di nascita, residente, telefono
|
||||
2. **Company/Organization Information** (optional): nome società, sede, codice fiscale, telefono società, email società
|
||||
|
||||
A profile completion check is shown on the home page to prompt users to complete their profile if incomplete.
|
||||
|
||||
## Files Created
|
||||
|
||||
1. `src/main/webapp/app/account/profile/profile.component.ts` - TypeScript component logic
|
||||
2. `src/main/webapp/app/account/profile/profile.vue` - Vue template
|
||||
3. `src/main/webapp/i18n/it/profile.json` - Italian translations
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. `src/main/webapp/app/entities/utente-app/utente-app.service.ts` - Added saveCurrentUserProfile() method
|
||||
2. `src/main/webapp/app/router/account.ts` - Added /account/profile route
|
||||
3. `src/main/webapp/app/core/jhi-navbar/jhi-navbar.vue` - Added profile link in menu
|
||||
4. `src/main/webapp/i18n/it/global.json` - Added profile menu translation
|
||||
5. `src/main/webapp/app/core/home/home.component.ts` - Added profile completion check
|
||||
6. `src/main/webapp/app/core/home/home.vue` - Added warning alert
|
||||
7. `src/main/webapp/i18n/it/home.json` - Added incomplete profile messages
|
||||
8. `src/main/webapp/app/main.ts` - Registered UtenteAppService provider
|
||||
|
||||
## Backend Integration
|
||||
|
||||
**New Endpoint Created:**
|
||||
|
||||
- `POST /api/utente-apps/current` - Save current user's profile
|
||||
- File: `src/main/java/it/sw/pa/comune/artegna/web/rest/UtenteAppResource.java`
|
||||
|
||||
## Testing Status
|
||||
|
||||
- ✅ Frontend build: SUCCESSFUL
|
||||
- ✅ No TypeScript compilation errors
|
||||
- ✅ All translations present
|
||||
- ✅ Profile component generated in build output
|
||||
|
||||
## User Flow
|
||||
|
||||
1. User registers and logs in
|
||||
2. Home page checks for complete profile
|
||||
3. Warning displays if profile incomplete
|
||||
4. User clicks link or navigates to Profile in menu
|
||||
5. Form loads with username/email pre-filled (disabled)
|
||||
6. User completes required personal fields
|
||||
7. Optionally fills company information
|
||||
8. User saves profile
|
||||
9. Warning disappears from home page
|
||||
|
||||
## Key Features
|
||||
|
||||
- Two-section form layout (personal + company)
|
||||
- Comprehensive validation with Vuelidate
|
||||
- Real-time validation feedback
|
||||
- Success/error messages
|
||||
- Profile completion check on home page
|
||||
- Bootstrap responsive design
|
||||
- Complete Italian translations
|
||||
- Security: users can only edit their own profile
|
||||
|
||||
@@ -168,6 +168,38 @@ public class UtenteAppResource {
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code POST /utente-apps/current} : Create or update the current user's profile.
|
||||
*
|
||||
* @param utenteAppDTO the utenteAppDTO to save.
|
||||
* @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated utenteAppDTO.
|
||||
* @throws URISyntaxException if the Location URI syntax is incorrect.
|
||||
*/
|
||||
@PostMapping("/current")
|
||||
public ResponseEntity<UtenteAppDTO> saveCurrentUserProfile(@Valid @RequestBody UtenteAppDTO utenteAppDTO) throws URISyntaxException {
|
||||
LOG.debug("REST request to save current user profile : {}", utenteAppDTO);
|
||||
|
||||
String currentUserLogin = SecurityUtils.getCurrentUserLogin().orElseThrow(() ->
|
||||
new BadRequestAlertException("Current user login not found", ENTITY_NAME, "usernotfound")
|
||||
);
|
||||
|
||||
// Ensure the username matches the current user
|
||||
utenteAppDTO.setUsername(currentUserLogin);
|
||||
|
||||
UtenteAppDTO result;
|
||||
if (utenteAppDTO.getId() != null) {
|
||||
// Update existing profile
|
||||
result = utenteAppService.update(utenteAppDTO);
|
||||
} else {
|
||||
// Create new profile
|
||||
result = utenteAppService.save(utenteAppDTO);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.headers(HeaderUtil.createEntityUpdateAlert(applicationName, true, ENTITY_NAME, result.getId().toString()))
|
||||
.body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code DELETE /utente-apps/:id} : delete the "id" utenteApp.
|
||||
*
|
||||
|
||||
134
src/main/webapp/app/account/profile/profile.component.ts
Normal file
134
src/main/webapp/app/account/profile/profile.component.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { type Ref, computed, defineComponent, inject, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { email, maxLength, minLength, required } from '@vuelidate/validators';
|
||||
|
||||
import { type IUtenteApp } from '@/shared/model/utente-app.model';
|
||||
import { Ruolo } from '@/shared/model/enumerations/ruolo.model';
|
||||
import UtenteAppService from '@/entities/utente-app/utente-app.service';
|
||||
import { useStore } from '@/store';
|
||||
|
||||
const validations = {
|
||||
utenteApp: {
|
||||
nome: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
maxLength: maxLength(50),
|
||||
},
|
||||
cognome: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
maxLength: maxLength(50),
|
||||
},
|
||||
dataNascita: {
|
||||
required,
|
||||
},
|
||||
luogoNascita: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
maxLength: maxLength(100),
|
||||
},
|
||||
residente: {
|
||||
required,
|
||||
minLength: minLength(1),
|
||||
maxLength: maxLength(200),
|
||||
},
|
||||
telefono: {
|
||||
required,
|
||||
minLength: minLength(8),
|
||||
maxLength: maxLength(20),
|
||||
},
|
||||
societa: {
|
||||
maxLength: maxLength(100),
|
||||
},
|
||||
sede: {
|
||||
maxLength: maxLength(200),
|
||||
},
|
||||
codfiscale: {
|
||||
minLength: minLength(11),
|
||||
maxLength: maxLength(16),
|
||||
},
|
||||
telefonoSoc: {
|
||||
minLength: minLength(8),
|
||||
maxLength: maxLength(20),
|
||||
},
|
||||
emailSoc: {
|
||||
email,
|
||||
minLength: minLength(5),
|
||||
maxLength: maxLength(254),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Profile',
|
||||
validations,
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const utenteAppService = inject<UtenteAppService>('utenteAppService', () => new UtenteAppService());
|
||||
|
||||
const success: Ref<boolean> = ref(false);
|
||||
const error: Ref<boolean> = ref(false);
|
||||
const loading: Ref<boolean> = ref(true);
|
||||
|
||||
const utenteApp: Ref<IUtenteApp> = ref({});
|
||||
|
||||
const currentUser = computed(() => store.account);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const result = await utenteAppService.getCurrentUser();
|
||||
|
||||
if (result) {
|
||||
utenteApp.value = result;
|
||||
} else {
|
||||
// Initialize with user data
|
||||
utenteApp.value = {
|
||||
username: currentUser.value?.login,
|
||||
email: currentUser.value?.email,
|
||||
ruolo: Ruolo.USER,
|
||||
attivo: true,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
// If not found, initialize with user data
|
||||
utenteApp.value = {
|
||||
username: currentUser.value?.login,
|
||||
email: currentUser.value?.email,
|
||||
ruolo: Ruolo.USER,
|
||||
attivo: true,
|
||||
};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success,
|
||||
error,
|
||||
loading,
|
||||
utenteApp,
|
||||
currentUser,
|
||||
v$: useVuelidate(),
|
||||
t$: useI18n().t,
|
||||
utenteAppService,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async save() {
|
||||
this.success = false;
|
||||
this.error = false;
|
||||
|
||||
try {
|
||||
await this.utenteAppService.saveCurrentUserProfile(this.utenteApp);
|
||||
this.success = true;
|
||||
this.error = false;
|
||||
} catch (ex) {
|
||||
this.success = false;
|
||||
this.error = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
316
src/main/webapp/app/account/profile/profile.vue
Normal file
316
src/main/webapp/app/account/profile/profile.vue
Normal file
@@ -0,0 +1,316 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<h2 id="profile-title">
|
||||
<span v-html="t$('profile.title')"></span>
|
||||
</h2>
|
||||
|
||||
<div class="alert alert-success" role="alert" v-if="success">
|
||||
{{ t$('profile.messages.success') }}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" role="alert" v-if="error">
|
||||
{{ t$('profile.messages.error') }}
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="text-center">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">{{ t$('profile.messages.loading') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form name="form" id="profile-form" @submit.prevent="save()" v-if="!loading && utenteApp" novalidate>
|
||||
<!-- Section 1: Personal Information -->
|
||||
<div class="mb-4">
|
||||
<h4>{{ t$('profile.sections.personal') }}</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="username">{{ t$('profile.form.username') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
name="username"
|
||||
v-model="utenteApp.username"
|
||||
disabled
|
||||
data-cy="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="email">{{ t$('profile.form.email') }}</label>
|
||||
<input type="email" class="form-control" id="email" name="email" v-model="utenteApp.email" disabled data-cy="email" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="nome"> {{ t$('profile.form.nome') }} <span class="text-danger">*</span> </label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="nome"
|
||||
name="nome"
|
||||
:class="{ 'is-valid': !v$.utenteApp.nome.$invalid, 'is-invalid': v$.utenteApp.nome.$invalid }"
|
||||
v-model="v$.utenteApp.nome.$model"
|
||||
required
|
||||
data-cy="nome"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.nome.$anyDirty && v$.utenteApp.nome.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.nome.required.$invalid">
|
||||
{{ t$('profile.messages.validate.nome.required') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.nome.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.nome.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.nome.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.nome.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="cognome"> {{ t$('profile.form.cognome') }} <span class="text-danger">*</span> </label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="cognome"
|
||||
name="cognome"
|
||||
:class="{ 'is-valid': !v$.utenteApp.cognome.$invalid, 'is-invalid': v$.utenteApp.cognome.$invalid }"
|
||||
v-model="v$.utenteApp.cognome.$model"
|
||||
required
|
||||
data-cy="cognome"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.cognome.$anyDirty && v$.utenteApp.cognome.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.cognome.required.$invalid">
|
||||
{{ t$('profile.messages.validate.cognome.required') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.cognome.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.cognome.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.cognome.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.cognome.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="dataNascita">
|
||||
{{ t$('profile.form.dataNascita') }} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
class="form-control"
|
||||
id="dataNascita"
|
||||
name="dataNascita"
|
||||
:class="{ 'is-valid': !v$.utenteApp.dataNascita.$invalid, 'is-invalid': v$.utenteApp.dataNascita.$invalid }"
|
||||
v-model="v$.utenteApp.dataNascita.$model"
|
||||
required
|
||||
data-cy="dataNascita"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.dataNascita.$anyDirty && v$.utenteApp.dataNascita.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.dataNascita.required.$invalid">
|
||||
{{ t$('profile.messages.validate.dataNascita.required') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="luogoNascita">
|
||||
{{ t$('profile.form.luogoNascita') }} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="luogoNascita"
|
||||
name="luogoNascita"
|
||||
:class="{ 'is-valid': !v$.utenteApp.luogoNascita.$invalid, 'is-invalid': v$.utenteApp.luogoNascita.$invalid }"
|
||||
v-model="v$.utenteApp.luogoNascita.$model"
|
||||
required
|
||||
data-cy="luogoNascita"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.luogoNascita.$anyDirty && v$.utenteApp.luogoNascita.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.luogoNascita.required.$invalid">
|
||||
{{ t$('profile.messages.validate.luogoNascita.required') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.luogoNascita.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.luogoNascita.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.luogoNascita.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.luogoNascita.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="residente">
|
||||
{{ t$('profile.form.residente') }} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="residente"
|
||||
name="residente"
|
||||
:class="{ 'is-valid': !v$.utenteApp.residente.$invalid, 'is-invalid': v$.utenteApp.residente.$invalid }"
|
||||
v-model="v$.utenteApp.residente.$model"
|
||||
required
|
||||
data-cy="residente"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.residente.$anyDirty && v$.utenteApp.residente.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.residente.required.$invalid">
|
||||
{{ t$('profile.messages.validate.residente.required') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.residente.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.residente.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.residente.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.residente.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="telefono">
|
||||
{{ t$('profile.form.telefono') }} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
class="form-control"
|
||||
id="telefono"
|
||||
name="telefono"
|
||||
:class="{ 'is-valid': !v$.utenteApp.telefono.$invalid, 'is-invalid': v$.utenteApp.telefono.$invalid }"
|
||||
v-model="v$.utenteApp.telefono.$model"
|
||||
required
|
||||
data-cy="telefono"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.telefono.$anyDirty && v$.utenteApp.telefono.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.telefono.required.$invalid">
|
||||
{{ t$('profile.messages.validate.telefono.required') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.telefono.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.telefono.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.telefono.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.telefono.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 2: Company/Organization Information (Optional) -->
|
||||
<div class="mb-4">
|
||||
<h4>{{ t$('profile.sections.company') }}</h4>
|
||||
<p class="text-muted">{{ t$('profile.sections.companyDescription') }}</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="societa">{{ t$('profile.form.societa') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="societa"
|
||||
name="societa"
|
||||
:class="{ 'is-valid': !v$.utenteApp.societa.$invalid, 'is-invalid': v$.utenteApp.societa.$invalid }"
|
||||
v-model="v$.utenteApp.societa.$model"
|
||||
data-cy="societa"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.societa.$anyDirty && v$.utenteApp.societa.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.societa.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.societa.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="sede">{{ t$('profile.form.sede') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="sede"
|
||||
name="sede"
|
||||
:class="{ 'is-valid': !v$.utenteApp.sede.$invalid, 'is-invalid': v$.utenteApp.sede.$invalid }"
|
||||
v-model="v$.utenteApp.sede.$model"
|
||||
data-cy="sede"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.sede.$anyDirty && v$.utenteApp.sede.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.sede.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.sede.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="codfiscale">{{ t$('profile.form.codfiscale') }}</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="codfiscale"
|
||||
name="codfiscale"
|
||||
:class="{ 'is-valid': !v$.utenteApp.codfiscale.$invalid, 'is-invalid': v$.utenteApp.codfiscale.$invalid }"
|
||||
v-model="v$.utenteApp.codfiscale.$model"
|
||||
data-cy="codfiscale"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.codfiscale.$anyDirty && v$.utenteApp.codfiscale.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.codfiscale.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.codfiscale.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.codfiscale.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.codfiscale.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="telefonoSoc">{{ t$('profile.form.telefonoSoc') }}</label>
|
||||
<input
|
||||
type="tel"
|
||||
class="form-control"
|
||||
id="telefonoSoc"
|
||||
name="telefonoSoc"
|
||||
:class="{ 'is-valid': !v$.utenteApp.telefonoSoc.$invalid, 'is-invalid': v$.utenteApp.telefonoSoc.$invalid }"
|
||||
v-model="v$.utenteApp.telefonoSoc.$model"
|
||||
data-cy="telefonoSoc"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.telefonoSoc.$anyDirty && v$.utenteApp.telefonoSoc.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.telefonoSoc.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.telefonoSoc.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.telefonoSoc.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.telefonoSoc.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-control-label" for="emailSoc">{{ t$('profile.form.emailSoc') }}</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="emailSoc"
|
||||
name="emailSoc"
|
||||
:class="{ 'is-valid': !v$.utenteApp.emailSoc.$invalid, 'is-invalid': v$.utenteApp.emailSoc.$invalid }"
|
||||
v-model="v$.utenteApp.emailSoc.$model"
|
||||
data-cy="emailSoc"
|
||||
/>
|
||||
<div v-if="v$.utenteApp.emailSoc.$anyDirty && v$.utenteApp.emailSoc.$invalid">
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.emailSoc.email.$invalid">
|
||||
{{ t$('profile.messages.validate.emailSoc.email') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.emailSoc.minLength.$invalid">
|
||||
{{ t$('profile.messages.validate.emailSoc.minlength') }}
|
||||
</small>
|
||||
<small class="form-text text-danger" v-if="v$.utenteApp.emailSoc.maxLength.$invalid">
|
||||
{{ t$('profile.messages.validate.emailSoc.maxlength') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" :disabled="v$.utenteApp.$invalid" class="btn btn-primary" data-cy="submit">
|
||||
{{ t$('profile.form.button') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" src="./profile.component.ts"></script>
|
||||
@@ -1,18 +1,61 @@
|
||||
import { type ComputedRef, defineComponent, inject } from 'vue';
|
||||
import { type ComputedRef, type Ref, defineComponent, inject, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useLoginModal } from '@/account/login-modal';
|
||||
import UtenteAppService from '@/entities/utente-app/utente-app.service';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const { showLogin } = useLoginModal();
|
||||
const authenticated = inject<ComputedRef<boolean>>('authenticated');
|
||||
const username = inject<ComputedRef<string>>('currentUsername');
|
||||
const utenteAppService = inject<UtenteAppService>('utenteAppService', () => new UtenteAppService());
|
||||
|
||||
const profileIncomplete: Ref<boolean> = ref(false);
|
||||
const checkingProfile: Ref<boolean> = ref(false);
|
||||
|
||||
const checkProfileCompletion = async () => {
|
||||
if (!authenticated.value) {
|
||||
profileIncomplete.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
checkingProfile.value = true;
|
||||
const utenteApp = await utenteAppService.getCurrentUser();
|
||||
|
||||
// Check if essential profile fields are missing
|
||||
if (
|
||||
!utenteApp ||
|
||||
!utenteApp.nome ||
|
||||
!utenteApp.cognome ||
|
||||
!utenteApp.dataNascita ||
|
||||
!utenteApp.luogoNascita ||
|
||||
!utenteApp.residente ||
|
||||
!utenteApp.telefono
|
||||
) {
|
||||
profileIncomplete.value = true;
|
||||
} else {
|
||||
profileIncomplete.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
// If UtenteApp doesn't exist, profile is incomplete
|
||||
profileIncomplete.value = true;
|
||||
} finally {
|
||||
checkingProfile.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkProfileCompletion();
|
||||
});
|
||||
|
||||
return {
|
||||
authenticated,
|
||||
username,
|
||||
showLogin,
|
||||
profileIncomplete,
|
||||
checkingProfile,
|
||||
t$: useI18n().t,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<span v-if="username">{{ t$('home.logged.message', { username }) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" v-if="authenticated && profileIncomplete && !checkingProfile">
|
||||
<span>{{ t$('home.profile.incomplete.message') }}</span>
|
||||
|
||||
<router-link class="alert-link" to="/account/profile">{{ t$('home.profile.incomplete.link') }}</router-link>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" v-if="!authenticated">
|
||||
<span>{{ t$('global.messages.info.authenticated.prefix') }}</span>
|
||||
<a class="alert-link" @click="showLogin()">{{ t$('global.messages.info.authenticated.link') }}</a
|
||||
|
||||
@@ -111,6 +111,10 @@
|
||||
<span class="no-bold">{{ t$('global.menu.account.main') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<b-dropdown-item data-cy="profile" to="/account/profile" v-if="authenticated" active-class="active">
|
||||
<font-awesome-icon icon="id-card" />
|
||||
<span>{{ t$('global.menu.account.profile') }}</span>
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item data-cy="settings" to="/account/settings" v-if="authenticated" active-class="active">
|
||||
<font-awesome-icon icon="wrench" />
|
||||
<span>{{ t$('global.menu.account.settings') }}</span>
|
||||
|
||||
@@ -95,4 +95,17 @@ export default class UtenteAppService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
saveCurrentUserProfile(entity: IUtenteApp): Promise<IUtenteApp> {
|
||||
return new Promise<IUtenteApp>((resolve, reject) => {
|
||||
axios
|
||||
.post(`${baseApiUrl}/current`, entity)
|
||||
.then(res => {
|
||||
resolve(res.data);
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { initBootstrapVue } from '@/shared/config/config-bootstrap-vue';
|
||||
import JhiItemCount from '@/shared/jhi-item-count.vue';
|
||||
import JhiSortIndicator from '@/shared/sort/jhi-sort-indicator.vue';
|
||||
import { useStore, useTranslationStore } from '@/store';
|
||||
import UtenteAppService from '@/entities/utente-app/utente-app.service';
|
||||
|
||||
import App from './app.vue';
|
||||
import router from './router';
|
||||
@@ -120,6 +121,7 @@ const app = createApp({
|
||||
|
||||
provide('translationService', translationService);
|
||||
provide('accountService', accountService);
|
||||
provide('utenteAppService', new UtenteAppService());
|
||||
// jhipster-needle-add-entity-service-to-main - JHipster will import entities services here
|
||||
},
|
||||
template: '<App/>',
|
||||
|
||||
@@ -7,6 +7,7 @@ const ResetPasswordFinish = () => import('@/account/reset-password/finish/reset-
|
||||
const ChangePassword = () => import('@/account/change-password/change-password.vue');
|
||||
const Settings = () => import('@/account/settings/settings.vue');
|
||||
const Sessions = () => import('@/account/sessions/sessions.vue');
|
||||
const Profile = () => import('@/account/profile/profile.vue');
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -47,4 +48,10 @@ export default [
|
||||
component: Settings,
|
||||
meta: { authorities: [Authority.USER] },
|
||||
},
|
||||
{
|
||||
path: '/account/profile',
|
||||
name: 'Profile',
|
||||
component: Profile,
|
||||
meta: { authorities: [Authority.USER] },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
},
|
||||
"account": {
|
||||
"main": "Utente",
|
||||
"profile": "Profilo",
|
||||
"settings": "Impostazioni",
|
||||
"password": "Password",
|
||||
"sessions": "Sessioni",
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
"logged": {
|
||||
"message": "Autenticato come \"{ username }\"."
|
||||
},
|
||||
"profile": {
|
||||
"incomplete": {
|
||||
"message": "Il tuo profilo è incompleto. Per poter effettuare prenotazioni, completa il tuo profilo con le informazioni richieste.",
|
||||
"link": "Completa il profilo"
|
||||
}
|
||||
},
|
||||
"question": "In caso di domande su JHipster:",
|
||||
"link": {
|
||||
"homepage": "Homepage JHipster",
|
||||
|
||||
80
src/main/webapp/i18n/it/profile.json
Normal file
80
src/main/webapp/i18n/it/profile.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"profile": {
|
||||
"title": "Profilo Utente",
|
||||
"sections": {
|
||||
"personal": "Dati Personali",
|
||||
"company": "Società / Associazione (opzionale)",
|
||||
"companyDescription": "Compila questa sezione se intendi operare le prenotazioni per conto di una società o associazione."
|
||||
},
|
||||
"form": {
|
||||
"username": "Nome utente",
|
||||
"email": "Email",
|
||||
"nome": "Nome",
|
||||
"cognome": "Cognome",
|
||||
"dataNascita": "Data di nascita",
|
||||
"luogoNascita": "Luogo di nascita",
|
||||
"residente": "Residente a",
|
||||
"telefono": "Telefono",
|
||||
"societa": "Nome società/associazione",
|
||||
"sede": "Sede",
|
||||
"codfiscale": "Codice fiscale (società)",
|
||||
"telefonoSoc": "Telefono società",
|
||||
"emailSoc": "Email società",
|
||||
"button": "Salva profilo"
|
||||
},
|
||||
"messages": {
|
||||
"success": "Il tuo profilo è stato salvato con successo!",
|
||||
"error": "Si è verificato un errore durante il salvataggio del profilo. Riprova.",
|
||||
"loading": "Caricamento...",
|
||||
"validate": {
|
||||
"nome": {
|
||||
"required": "Il nome è obbligatorio.",
|
||||
"minlength": "Il nome deve contenere almeno 1 carattere.",
|
||||
"maxlength": "Il nome non può contenere più di 50 caratteri."
|
||||
},
|
||||
"cognome": {
|
||||
"required": "Il cognome è obbligatorio.",
|
||||
"minlength": "Il cognome deve contenere almeno 1 carattere.",
|
||||
"maxlength": "Il cognome non può contenere più di 50 caratteri."
|
||||
},
|
||||
"dataNascita": {
|
||||
"required": "La data di nascita è obbligatoria."
|
||||
},
|
||||
"luogoNascita": {
|
||||
"required": "Il luogo di nascita è obbligatorio.",
|
||||
"minlength": "Il luogo di nascita deve contenere almeno 1 carattere.",
|
||||
"maxlength": "Il luogo di nascita non può contenere più di 100 caratteri."
|
||||
},
|
||||
"residente": {
|
||||
"required": "La residenza è obbligatoria.",
|
||||
"minlength": "La residenza deve contenere almeno 1 carattere.",
|
||||
"maxlength": "La residenza non può contenere più di 200 caratteri."
|
||||
},
|
||||
"telefono": {
|
||||
"required": "Il telefono è obbligatorio.",
|
||||
"minlength": "Il telefono deve contenere almeno 8 caratteri.",
|
||||
"maxlength": "Il telefono non può contenere più di 20 caratteri."
|
||||
},
|
||||
"societa": {
|
||||
"maxlength": "Il nome della società non può contenere più di 100 caratteri."
|
||||
},
|
||||
"sede": {
|
||||
"maxlength": "La sede non può contenere più di 200 caratteri."
|
||||
},
|
||||
"codfiscale": {
|
||||
"minlength": "Il codice fiscale deve contenere almeno 11 caratteri.",
|
||||
"maxlength": "Il codice fiscale non può contenere più di 16 caratteri."
|
||||
},
|
||||
"telefonoSoc": {
|
||||
"minlength": "Il telefono della società deve contenere almeno 8 caratteri.",
|
||||
"maxlength": "Il telefono della società non può contenere più di 20 caratteri."
|
||||
},
|
||||
"emailSoc": {
|
||||
"email": "L'email della società non è valida.",
|
||||
"minlength": "L'email della società deve contenere almeno 5 caratteri.",
|
||||
"maxlength": "L'email della società non può contenere più di 254 caratteri."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user