mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-09 15:57:59 -05:00
Email+username validations + Strava callback view text + edit profile now uses unified modal
[backend] add get user by email crud and route logic [frontend] add getUserByUsername logic and changed current to getUserContainsUsername [frontend] add text to Strava callback view [frontend] added username and email form validations to add and edit logic [frontend] profile edit logic now uses unified modal
This commit is contained in:
@@ -162,6 +162,32 @@ def get_user_by_username(username: str, db: Session):
|
||||
) from err
|
||||
|
||||
|
||||
def get_user_by_email(email: str, db: Session):
|
||||
try:
|
||||
# Get the user from the database
|
||||
user = (
|
||||
db.query(users_models.User).filter(users_models.User.email == email).first()
|
||||
)
|
||||
|
||||
# If the user was not found, return None
|
||||
if user is None:
|
||||
return None
|
||||
|
||||
# Format the birthdate
|
||||
user = users_utils.format_user_birthdate(user)
|
||||
|
||||
# Return the user
|
||||
return user
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(f"Error in get_user_by_email: {err}", "error", exc=err)
|
||||
# Raise an HTTPException with a 500 Internal Server Error status code
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Internal Server Error",
|
||||
) from err
|
||||
|
||||
|
||||
def get_user_by_id(user_id: int, db: Session):
|
||||
try:
|
||||
# Get the user from the database
|
||||
@@ -247,7 +273,7 @@ def create_user(user: users_schema.UserCreate, db: Session):
|
||||
# Create a new user
|
||||
db_user = users_models.User(
|
||||
**user.model_dump(exclude={"password"}),
|
||||
password=session_security.hash_password(user.password)
|
||||
password=session_security.hash_password(user.password),
|
||||
)
|
||||
|
||||
# Add the user to the database
|
||||
|
||||
@@ -91,7 +91,25 @@ async def read_users_username(
|
||||
],
|
||||
):
|
||||
# Get the user from the database by username
|
||||
return users_crud.get_user_by_username(username=username, db=db)
|
||||
return users_crud.get_user_by_username(username, db)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/email/{email}",
|
||||
response_model=users_schema.User | None,
|
||||
)
|
||||
async def read_users_email(
|
||||
email: str,
|
||||
check_scopes: Annotated[
|
||||
Callable, Security(session_security.check_scopes, scopes=["users:read"])
|
||||
],
|
||||
db: Annotated[
|
||||
Session,
|
||||
Depends(core_database.get_db),
|
||||
],
|
||||
):
|
||||
# Get the users from the database by email
|
||||
return users_crud.get_user_by_email(email, db)
|
||||
|
||||
|
||||
@router.get("/id/{user_id}", response_model=users_schema.User)
|
||||
|
||||
@@ -5,6 +5,8 @@ files:
|
||||
translation: /frontend/app/src/i18n/%two_letters_code%/components/%file_name%.%file_extension%
|
||||
- source: /frontend/app/src/i18n/us/gears/*.json
|
||||
translation: /frontend/app/src/i18n/%two_letters_code%/gears/%file_name%.%file_extension%
|
||||
- source: /frontend/app/src/i18n/us/strava/*.json
|
||||
translation: /frontend/app/src/i18n/%two_letters_code%/strava/%file_name%.%file_extension%
|
||||
- source: /frontend/app/src/i18n/us/components/activities/*.json
|
||||
translation: /frontend/app/src/i18n/%two_letters_code%/components/activities/%original_file_name%
|
||||
- source: /frontend/app/src/i18n/us/components/activities/modals/*.json
|
||||
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
searchResults.value = await users.getUserByUsername(query);
|
||||
searchResults.value = await users.getUserContainsUsername(query);
|
||||
} catch (error) {
|
||||
push.error(`${t('generalItems.errorFetchingInfo')} - ${error}`)
|
||||
}
|
||||
|
||||
@@ -32,73 +32,7 @@
|
||||
<a class="mt-2 w-100 btn btn-primary" href="#" role="button" data-bs-toggle="modal" data-bs-target="#editProfileModal">{{ $t("settingsUserProfileZone.buttonEditProfile") }}</a>
|
||||
|
||||
<!-- Modal edit user -->
|
||||
<div class="modal fade" id="editProfileModal" tabindex="-1" aria-labelledby="editProfileModal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="editProfileModal">{{ $t("usersListComponent.modalEditUserTitle") }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form @submit.prevent="submitEditUserForm">
|
||||
<div class="modal-body">
|
||||
<label for="userImgEdit"><b>{{ $t("settingsUsersZone.addUserModalUserPhotoLabel") }}</b></label>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input class="form-control" type="file" accept="image/*" name="userImgEdit" id="userImgEdit" @change="handleFileChange">
|
||||
</div>
|
||||
<div class="col" v-if="authStore.user.photo_path">
|
||||
<a class="w-100 btn btn-danger" data-bs-dismiss="modal" @click="submitDeleteUserPhoto">{{ $t("usersListComponent.modalEditUserDeleteUserPhotoButton") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- username fields -->
|
||||
<label for="userUsernameEdit"><b>* {{ $t("settingsUsersZone.addUserModalUsernameLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userUsernameEdit" :placeholder='$t("settingsUsersZone.addUserModalUsernamePlaceholder")' maxlength="250" v-model="editUserUsername" required>
|
||||
<!-- name fields -->
|
||||
<label for="userNameEdit"><b>* {{ $t("settingsUsersZone.addUserModalNameLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userNameEdit" :placeholder='$t("settingsUsersZone.addUserModalNamePlaceholder")' maxlength="250" v-model="editUserName" required>
|
||||
<!-- email fields -->
|
||||
<label for="userEmailEdit"><b>* {{ $t("settingsUsersZone.addUserModalEmailLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userEmailEdit" :placeholder='$t("settingsUsersZone.addUserModalEmailPlaceholder")' maxlength="45" v-model="editUserEmail" required>
|
||||
<!-- city fields -->
|
||||
<label for="userCityEdit"><b>{{ $t("settingsUsersZone.addUserModalTownLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userCityEdit" :placeholder='$t("settingsUsersZone.addUserModalTownPlaceholder")' maxlength="45" v-model="editUserTown">
|
||||
<!-- birth date fields -->
|
||||
<label for="userBirthDateEdit"><b>{{ $t("settingsUsersZone.addUserModalBirthdayLabel") }}</b></label>
|
||||
<input class="form-control" type="date" name="userBirthDateEdit" v-model="editUserBirthdate">
|
||||
<!-- gender fields -->
|
||||
<label for="userGenderEdit"><b>* {{ $t("settingsUsersZone.addUserModalGenderLabel") }}</b></label>
|
||||
<select class="form-control" name="userGenderEdit" v-model="editUserGender" required>
|
||||
<option value="1">{{ $t("settingsUsersZone.addUserModalGenderOption1") }}</option>
|
||||
<option value="2">{{ $t("settingsUsersZone.addUserModalGenderOption2") }}</option>
|
||||
</select>
|
||||
<!-- units fields -->
|
||||
<label for="userUnitsEdit"><b>* {{ $t("settingsUsersZone.addUserModalUnitsLabel") }}</b></label>
|
||||
<select class="form-control" name="userUnitsEdit" v-model="editUserUnits" required>
|
||||
<option value="1">{{ $t("settingsUsersZone.addUserModalUnitsOption1") }}</option>
|
||||
<option value="2">{{ $t("settingsUsersZone.addUserModalUnitsOption2") }}</option>
|
||||
</select>
|
||||
<!-- height fields -->
|
||||
<label for="userHeightEdit"><b>{{ $t("settingsUsersZone.addUserModalHeightLabel") }} (cm)</b></label>
|
||||
<input class="form-control" type="number" name="userHeightEdit" :placeholder='$t("settingsUsersZone.addUserModalHeightPlaceholder") + " (cm)"' v-model="editUserHeight">
|
||||
<!-- preferred language fields -->
|
||||
<label for="userPreferredLanguageEdit"><b>* {{ $t("settingsUsersZone.addUserModalUserPreferredLanguageLabel") }}</b></label>
|
||||
<select class="form-control" name="userPreferredLanguageEdit" v-model="editUserPreferredLanguage" required>
|
||||
<option value="us">{{ $t("settingsUsersZone.addUserModalPreferredLanguageOption1") }}</option>
|
||||
<option value="ca">{{ $t("settingsUsersZone.addUserModalPreferredLanguageOption2") }}</option>
|
||||
<option value="pt">{{ $t("settingsUsersZone.addUserModalPreferredLanguageOption3") }}</option>
|
||||
</select>
|
||||
<p>* {{ $t("generalItems.requiredField") }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userEdit" data-bs-dismiss="modal">{{ $t("usersListComponent.modalEditUserTitle") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UsersAddEditUserModalComponent :action="'profile'" :user="authStore.user"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- user name -->
|
||||
@@ -164,7 +98,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
// Importing the services
|
||||
import { profile } from "@/services/profileService";
|
||||
@@ -172,83 +105,18 @@ import { profile } from "@/services/profileService";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
// Import Notivue push
|
||||
import { push } from "notivue";
|
||||
// Import units utils
|
||||
import { cmToFeetInches } from "@/utils/unitsUtils";
|
||||
// Importing the components
|
||||
import UserAvatarComponent from "../Users/UserAvatarComponent.vue";
|
||||
import UsersAddEditUserModalComponent from "@/components/Settings/SettingsUsersZone/UsersAddEditUserModalComponent.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatarComponent,
|
||||
UsersAddEditUserModalComponent,
|
||||
},
|
||||
setup() {
|
||||
const authStore = useAuthStore();
|
||||
const { t, locale } = useI18n();
|
||||
const editUserPhotoFile = ref(null);
|
||||
const editUserUsername = ref(authStore.user.username);
|
||||
const editUserName = ref(authStore.user.name);
|
||||
const editUserEmail = ref(authStore.user.email);
|
||||
const editUserTown = ref(authStore.user.city);
|
||||
const editUserBirthdate = ref(authStore.user.birthdate);
|
||||
const editUserGender = ref(authStore.user.gender);
|
||||
const editUserUnits = ref(authStore.user.units);
|
||||
const editUserHeight = ref(authStore.user.height);
|
||||
const editUserPreferredLanguage = ref(authStore.user.preferred_language);
|
||||
const editUserAccessType = ref(authStore.user.access_type);
|
||||
const editUserPhotoPath = ref(authStore.user.photo_path);
|
||||
const { feet, inches } = cmToFeetInches(authStore.user.height);
|
||||
|
||||
async function handleFileChange(event) {
|
||||
editUserPhotoFile.value = event.target.files?.[0] ?? null;
|
||||
}
|
||||
|
||||
async function submitEditUserForm() {
|
||||
try {
|
||||
if (editUserHeight.value === "" || editUserHeight.value === 0) {
|
||||
editUserHeight.value = null;
|
||||
}
|
||||
const data = {
|
||||
id: authStore.user.id,
|
||||
username: editUserUsername.value,
|
||||
name: editUserName.value,
|
||||
email: editUserEmail.value,
|
||||
city: editUserTown.value,
|
||||
birthdate: editUserBirthdate.value,
|
||||
gender: editUserGender.value,
|
||||
units: editUserUnits.value,
|
||||
height: editUserHeight.value,
|
||||
preferred_language: editUserPreferredLanguage.value,
|
||||
access_type: editUserAccessType.value,
|
||||
photo_path: editUserPhotoPath.value,
|
||||
is_active: 1,
|
||||
};
|
||||
|
||||
// If there is a photo, upload it and get the photo url.
|
||||
if (editUserPhotoFile.value) {
|
||||
try {
|
||||
data.photo_path = await profile.uploadProfileImage(
|
||||
editUserPhotoFile.value,
|
||||
);
|
||||
} catch (error) {
|
||||
// Set the error message
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
await profile.editProfile(data);
|
||||
|
||||
// Save the user data in the local storage and in the store.
|
||||
authStore.setUser(data, locale);
|
||||
|
||||
// Set the success message and show the success alert.
|
||||
push.success(t("usersListComponent.userEditSuccessMessage"));
|
||||
} catch (error) {
|
||||
// If there is an error, set the error message and show the error alert.
|
||||
push.error(
|
||||
`${t("usersListComponent.userEditErrorMessage")} - ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitDeleteUserPhoto() {
|
||||
try {
|
||||
@@ -260,7 +128,7 @@ export default {
|
||||
user.photo_path = null;
|
||||
|
||||
// Save the user data in the local storage and in the store.
|
||||
authStore.setUser(user, locale);
|
||||
authStore.setUser(user, authStore.session_id, locale);
|
||||
|
||||
// Set the success message and show the success alert.
|
||||
push.success(t("usersListComponent.userPhotoDeleteSuccessMessage"));
|
||||
@@ -275,21 +143,7 @@ export default {
|
||||
return {
|
||||
authStore,
|
||||
t,
|
||||
editUserUsername,
|
||||
editUserName,
|
||||
editUserEmail,
|
||||
editUserTown,
|
||||
editUserBirthdate,
|
||||
editUserGender,
|
||||
editUserUnits,
|
||||
editUserHeight,
|
||||
editUserPreferredLanguage,
|
||||
editUserAccessType,
|
||||
submitEditUserForm,
|
||||
submitDeleteUserPhoto,
|
||||
handleFileChange,
|
||||
feet,
|
||||
inches,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -100,7 +100,7 @@ export default {
|
||||
}
|
||||
try {
|
||||
// Fetch the users based on the search username.
|
||||
usersArray.value = await users.getUserByUsername(searchUsername.value);
|
||||
usersArray.value = await users.getUserContainsUsername(searchUsername.value);
|
||||
} catch (error) {
|
||||
// If there is an error, set the error message and show the error alert.
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<!-- Modal add/edit user -->
|
||||
<div class="modal fade" :id="action == 'add' ? 'addUserModal' : (action == 'edit' ? editUserId : '')" tabindex="-1" :aria-labelledby="action == 'add' ? 'addUserModal' : (action == 'edit' ? editUserId : '')" aria-hidden="true">
|
||||
<div class="modal fade" :id="action == 'add' ? 'addUserModal' : (action == 'edit' ? editUserModalId : (action == 'profile' ? 'editProfileModal' : ''))" tabindex="-1" :aria-labelledby="action == 'add' ? 'addUserModal' : (action == 'edit' ? editUserModalId : (action == 'profile' ? 'editProfileModal' : ''))" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="addUserModal" v-if="action == 'add'">{{ $t("settingsUsersZone.buttonAddUser") }}</h1>
|
||||
<h1 class="modal-title fs-5" id="editUserId" v-else>{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</h1>
|
||||
<h1 class="modal-title fs-5" id="editUserModalId" v-else-if="action == 'edit'">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</h1>
|
||||
<h1 class="modal-title fs-5" id="editProfileModal" v-else>{{ $t("usersAddEditUserModalComponent.addEditUserModalEditProfileTitle") }}</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
@@ -23,13 +24,22 @@
|
||||
</div>
|
||||
<!-- username fields -->
|
||||
<label for="userUsernameAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalUsernameLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userUsernameAddEdit" :placeholder='$t("usersAddEditUserModalComponent.addEditUserModalUsernamePlaceholder")' maxlength="250" v-model="newEditUserUsername" required>
|
||||
<input class="form-control" :class="{ 'is-invalid': !isUsernameExists }" type="text" name="userUsernameAddEdit" :placeholder='$t("usersAddEditUserModalComponent.addEditUserModalUsernamePlaceholder")' maxlength="250" v-model="newEditUserUsername" required>
|
||||
<div id="validationUsernameFeedback" class="invalid-feedback" v-if="!isUsernameExists">
|
||||
{{ $t("generalItems.errorUsernameAlreadyExistsFeedback") }}
|
||||
</div>
|
||||
<!-- name fields -->
|
||||
<label for="userNameAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalNameLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userNameAddEdit" :placeholder='$t("usersAddEditUserModalComponent.addEditUserModalNamePlaceholder")' maxlength="250" v-model="newEditUserName" required>
|
||||
<!-- email fields -->
|
||||
<label for="userEmailAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalEmailLabel") }}</b></label>
|
||||
<input class="form-control" type="text" name="userEmailAddEdit" :placeholder='$t("usersAddEditUserModalComponent.addEditUserModalEmailPlaceholder")' maxlength="45" v-model="newEditUserEmail" required>
|
||||
<input class="form-control" :class="{ 'is-invalid': !isEmailValid || !isEmailExists }" type="text" name="userEmailAddEdit" :placeholder='$t("usersAddEditUserModalComponent.addEditUserModalEmailPlaceholder")' maxlength="45" v-model="newEditUserEmail" required>
|
||||
<div id="validationEmailFeedback" class="invalid-feedback" v-if="!isEmailValid">
|
||||
{{ $t("generalItems.errorEmailNotValidFeedback") }}
|
||||
</div>
|
||||
<div id="validationEmailFeedback" class="invalid-feedback" v-else-if="!isEmailExists">
|
||||
{{ $t("generalItems.errorEmailAlreadyExistsFeedback") }}
|
||||
</div>
|
||||
<!-- password fields -->
|
||||
<div v-if="action == 'add'">
|
||||
<label for="passUserAdd"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalPasswordLabel") }}</b></label>
|
||||
@@ -86,24 +96,29 @@
|
||||
<option value="fr">{{ $t("usersAddEditUserModalComponent.addEditUserModalPreferredLanguageOption5") }}</option>
|
||||
</select>
|
||||
<!-- access type fields -->
|
||||
<label for="userTypeAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeLabel") }}</b></label>
|
||||
<select class="form-control" name="userTypeAddEdit" v-model="newEditUserAccessType" required>
|
||||
<option value="1">{{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeOption1") }}</option>
|
||||
<option value="2">{{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeOption2") }}</option>
|
||||
</select>
|
||||
<div v-if="action != 'profile'">
|
||||
<label for="userTypeAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeLabel") }}</b></label>
|
||||
<select class="form-control" name="userTypeAddEdit" v-model="newEditUserAccessType" required>
|
||||
<option value="1">{{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeOption1") }}</option>
|
||||
<option value="2">{{ $t("usersAddEditUserModalComponent.addEditUserModalUserTypeOption2") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- user is_active fields -->
|
||||
<label for="userIsActiveAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveLabel") }}</b></label>
|
||||
<select class="form-control" name="userIsActiveAddEdit" v-model="newEditUserIsActive" required>
|
||||
<option value="1">{{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveOption1") }}</option>
|
||||
<option value="2">{{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveOption2") }}</option>
|
||||
</select>
|
||||
<div v-if="action != 'profile'">
|
||||
<label for="userIsActiveAddEdit"><b>* {{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveLabel") }}</b></label>
|
||||
<select class="form-control" name="userIsActiveAddEdit" v-model="newEditUserIsActive" required>
|
||||
<option value="1">{{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveOption1") }}</option>
|
||||
<option value="2">{{ $t("usersAddEditUserModalComponent.addEditUserModalIsActiveOption2") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<p>* {{ $t("generalItems.requiredField") }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ $t("generalItems.buttonClose") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userAdd" data-bs-dismiss="modal" v-if="action == 'add'" :disabled="!isPasswordValid">{{ $t("usersAddEditUserModalComponent.addEditUserModalAddTitle") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userEdit" data-bs-dismiss="modal" v-else :disabled="!isFeetValid || !isInchesValid">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userAdd" data-bs-dismiss="modal" v-if="action == 'add'" :disabled="!isPasswordValid || !isUsernameExists || !isEmailValid || !isEmailExists">{{ $t("usersAddEditUserModalComponent.addEditUserModalAddTitle") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userEdit" data-bs-dismiss="modal" v-else-if="action == 'edit'" :disabled="!isFeetValid || !isInchesValid || !isUsernameExists || !isEmailValid || !isEmailExists">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditTitle") }}</button>
|
||||
<button type="submit" class="btn btn-success" name="userEdit" data-bs-dismiss="modal" v-else :disabled="!isFeetValid || !isInchesValid || !isUsernameExists || !isEmailValid || !isEmailExists">{{ $t("usersAddEditUserModalComponent.addEditUserModalEditProfileTitle") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -112,13 +127,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, computed, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
// Import the stores
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
// import lodash
|
||||
import { debounce } from 'lodash';
|
||||
// Import Notivue push
|
||||
import { push } from "notivue";
|
||||
// Importing the services
|
||||
import { profile } from "@/services/profileService";
|
||||
import { users } from "@/services/usersService";
|
||||
// Import units utils
|
||||
import { cmToFeetInches, feetAndInchesToCm } from "@/utils/unitsUtils";
|
||||
@@ -139,7 +157,7 @@ export default {
|
||||
const authStore = useAuthStore();
|
||||
const { t, locale } = useI18n();
|
||||
// edit user specific variables
|
||||
const editUserId = ref("");
|
||||
const editUserModalId = ref("");
|
||||
// edit and add user variables
|
||||
const newEditUserPhotoFile = ref(null);
|
||||
const newEditUserUsername = ref("");
|
||||
@@ -162,6 +180,71 @@ export default {
|
||||
const newEditUserAccessType = ref(1);
|
||||
const newEditUserIsActive = ref(1);
|
||||
const newEditUserPhotoPath = ref(null);
|
||||
const isUsernameExists = ref(true);
|
||||
const validateUsernameExists = debounce(async () => {
|
||||
let tryValidate = false;
|
||||
if (props.action === 'edit' || props.action === 'profile') {
|
||||
if (newEditUserUsername.value !== props.user.username) {
|
||||
tryValidate = true;
|
||||
}
|
||||
} else {
|
||||
if (props.action === 'add') {
|
||||
if (newEditUserUsername.value !== "") {
|
||||
tryValidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tryValidate) {
|
||||
try {
|
||||
if (await users.getUserByUsername(newEditUserUsername.value)) {
|
||||
isUsernameExists.value = false;
|
||||
} else {
|
||||
isUsernameExists.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
} else {
|
||||
isUsernameExists.value = true;
|
||||
}
|
||||
}, 500);
|
||||
const isEmailExists = ref(true);
|
||||
// Regular expression for email validation
|
||||
const isEmailValid = computed(() => {
|
||||
const emailRegex = /^[^\s@]{1,}@[^\s@]{2,}\.[^\s@]{2,}$/;
|
||||
return emailRegex.test(newEditUserEmail.value);
|
||||
});
|
||||
const validateEmailExists = debounce(async () => {
|
||||
let tryValidate = false;
|
||||
if (props.action === 'edit' || props.action === 'profile') {
|
||||
if (newEditUserEmail.value !== props.user.email) {
|
||||
tryValidate = true;
|
||||
}
|
||||
} else {
|
||||
if (props.action === 'add') {
|
||||
if (newEditUserEmail.value !== "") {
|
||||
tryValidate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tryValidate) {
|
||||
if (isEmailValid.value) {
|
||||
try {
|
||||
if (await users.getUserByEmail(newEditUserEmail.value)) {
|
||||
isEmailExists.value = false;
|
||||
} else {
|
||||
isEmailExists.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isEmailExists.value = true;
|
||||
}
|
||||
}, 500);
|
||||
// new user variables
|
||||
const newUserPassword = ref("");
|
||||
const isPasswordValid = computed(() => {
|
||||
@@ -171,7 +254,9 @@ export default {
|
||||
});
|
||||
|
||||
if (props.user) {
|
||||
editUserId.value = `editUserModal${props.user.id}`;
|
||||
if (props.action === 'edit') {
|
||||
editUserModalId.value = `editUserModal${props.user.id}`;
|
||||
}
|
||||
newEditUserUsername.value = props.user.username;
|
||||
newEditUserName.value = props.user.name;
|
||||
newEditUserEmail.value = props.user.email;
|
||||
@@ -282,24 +367,36 @@ export default {
|
||||
is_active: newEditUserIsActive.value,
|
||||
};
|
||||
|
||||
await users.editUser(data.id, data);
|
||||
// If there is a photo, upload it and get the photo url.
|
||||
if (newEditUserPhotoFile.value) {
|
||||
try {
|
||||
if (props.action === 'profile') {
|
||||
data.photo_path = await profile.uploadProfileImage(
|
||||
newEditUserPhotoFile.value,
|
||||
);
|
||||
} else {
|
||||
data.photo_path = await users.uploadImage(
|
||||
newEditUserPhotoFile.value,
|
||||
data.id,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// Set the error message
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a photo, upload it and get the photo url.
|
||||
if (newEditUserPhotoFile.value) {
|
||||
try {
|
||||
data.photo_path = await users.uploadImage(
|
||||
newEditUserPhotoFile.value,
|
||||
data.id,
|
||||
);
|
||||
} catch (error) {
|
||||
// Set the error message
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
}
|
||||
if (props.action === 'profile') {
|
||||
await profile.editProfile(data);
|
||||
} else {
|
||||
await users.editUser(data.id, data);
|
||||
}
|
||||
|
||||
emit("editedUser", data);
|
||||
if (props.action === 'edit') {
|
||||
emit("editedUser", data);
|
||||
}
|
||||
|
||||
if (data.id === authStore.user.id) {
|
||||
if (data.id === authStore.user.id || props.action === 'profile') {
|
||||
authStore.setUser(data, authStore.session_id, locale);
|
||||
}
|
||||
|
||||
@@ -334,10 +431,17 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Watchers
|
||||
// Watch the newEditUserUsername variable.
|
||||
watch(newEditUserUsername, validateUsernameExists, { immediate: false });
|
||||
|
||||
// Watch the newEditUserEmail variable.
|
||||
watch(newEditUserEmail, validateEmailExists, { immediate: false });
|
||||
|
||||
return {
|
||||
authStore,
|
||||
t,
|
||||
editUserId,
|
||||
editUserModalId,
|
||||
newEditUserPhotoFile,
|
||||
newEditUserUsername,
|
||||
newEditUserName,
|
||||
@@ -354,6 +458,9 @@ export default {
|
||||
newEditUserIsActive,
|
||||
newEditUserPhotoPath,
|
||||
newUserPassword,
|
||||
isUsernameExists,
|
||||
isEmailExists,
|
||||
isEmailValid,
|
||||
isPasswordValid,
|
||||
isFeetValid,
|
||||
isInchesValid,
|
||||
|
||||
@@ -99,83 +99,10 @@ export default {
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const userProp = ref(props.user);
|
||||
const editUserPhotoFile = ref(null);
|
||||
const editUserUsername = ref(userProp.value.username);
|
||||
const editUserName = ref(userProp.value.name);
|
||||
const editUserEmail = ref(userProp.value.email);
|
||||
const editUserTown = ref(userProp.value.city);
|
||||
const editUserBirthdate = ref(userProp.value.birthdate);
|
||||
const editUserGender = ref(userProp.value.gender);
|
||||
const editUserUnits = ref(userProp.value.units);
|
||||
const editUserHeight = ref(userProp.value.height);
|
||||
const editUserPreferredLanguage = ref(userProp.value.preferred_language);
|
||||
const editUserAccessType = ref(userProp.value.access_type);
|
||||
const editUserIsActive = ref(userProp.value.is_active);
|
||||
const userDetails = ref(false);
|
||||
const userSessions = ref([]);
|
||||
const isLoading = ref(true);
|
||||
|
||||
async function handleFileChange(event) {
|
||||
editUserPhotoFile.value = event.target.files?.[0] ?? null;
|
||||
}
|
||||
|
||||
async function submitEditUserForm() {
|
||||
try {
|
||||
const data = {
|
||||
id: userProp.value.id,
|
||||
username: editUserUsername.value,
|
||||
name: editUserName.value,
|
||||
email: editUserEmail.value,
|
||||
city: editUserTown.value,
|
||||
birthdate: editUserBirthdate.value,
|
||||
gender: editUserGender.value,
|
||||
units: editUserUnits.value,
|
||||
height: editUserHeight.value,
|
||||
preferred_language: editUserPreferredLanguage.value,
|
||||
access_type: editUserAccessType.value,
|
||||
photo_path: null,
|
||||
is_active: editUserIsActive.value,
|
||||
};
|
||||
|
||||
await users.editUser(userProp.value.id, data);
|
||||
|
||||
// If there is a photo, upload it and get the photo url.
|
||||
if (editUserPhotoFile.value) {
|
||||
try {
|
||||
userProp.value.photo_path = await users.uploadImage(
|
||||
editUserPhotoFile.value,
|
||||
userProp.value.id,
|
||||
);
|
||||
} catch (error) {
|
||||
// Set the error message
|
||||
push.error(`${t("generalItems.errorFetchingInfo")} - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
userProp.value.username = editUserUsername.value;
|
||||
userProp.value.name = editUserName.value;
|
||||
userProp.value.email = editUserEmail.value;
|
||||
userProp.value.city = editUserTown.value;
|
||||
userProp.value.birthdate = editUserBirthdate.value;
|
||||
userProp.value.city = editUserTown.value;
|
||||
userProp.value.birthdate = editUserBirthdate.value;
|
||||
userProp.value.gender = editUserGender.value;
|
||||
userProp.value.units = editUserUnits.value;
|
||||
userProp.value.height = editUserHeight.value;
|
||||
userProp.value.preferred_language = editUserPreferredLanguage.value;
|
||||
userProp.value.access_type = editUserAccessType.value;
|
||||
userProp.value.is_active = editUserIsActive.value;
|
||||
|
||||
// Set the success message and show the success alert.
|
||||
push.success(t("usersListComponent.userEditSuccessMessage"));
|
||||
} catch (error) {
|
||||
// If there is an error, set the error message and show the error alert.
|
||||
push.error(
|
||||
`${t("usersListComponent.userEditErrorMessage")} - ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitDeleteUser() {
|
||||
try {
|
||||
await users.deleteUser(userProp.value.id);
|
||||
@@ -245,19 +172,6 @@ export default {
|
||||
return {
|
||||
t,
|
||||
authStore,
|
||||
editUserUsername,
|
||||
editUserName,
|
||||
editUserEmail,
|
||||
editUserTown,
|
||||
editUserBirthdate,
|
||||
editUserGender,
|
||||
editUserUnits,
|
||||
editUserHeight,
|
||||
editUserPreferredLanguage,
|
||||
editUserAccessType,
|
||||
editUserIsActive,
|
||||
submitEditUserForm,
|
||||
handleFileChange,
|
||||
submitDeleteUser,
|
||||
submitDeleteUserPhoto,
|
||||
showUserDetails,
|
||||
|
||||
4
frontend/app/src/i18n/ca/strava/stravaCallbackView.json
Normal file
4
frontend/app/src/i18n/ca/strava/stravaCallbackView.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stravaCallbackViewTitle1": "Handling Strava callback",
|
||||
"stravaCallbackViewTitle2": "Please wait while Strava is being linked to your account"
|
||||
}
|
||||
4
frontend/app/src/i18n/de/strava/stravaCallbackView.json
Normal file
4
frontend/app/src/i18n/de/strava/stravaCallbackView.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stravaCallbackViewTitle1": "Handling Strava callback",
|
||||
"stravaCallbackViewTitle2": "Please wait while Strava is being linked to your account"
|
||||
}
|
||||
4
frontend/app/src/i18n/fr/strava/stravaCallbackView.json
Normal file
4
frontend/app/src/i18n/fr/strava/stravaCallbackView.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stravaCallbackViewTitle1": "Handling Strava callback",
|
||||
"stravaCallbackViewTitle2": "Please wait while Strava is being linked to your account"
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import caHomeView from './ca/homeView.json';
|
||||
import caLoginView from './ca/loginView.json';
|
||||
import caGearsView from './ca/gears/gearsView.json';
|
||||
import caGearView from './ca/gears/gearView.json';
|
||||
import caStravaCallbackView from './ca/strava/stravaCallbackView.json';
|
||||
import caActivityView from './ca/activityView.json';
|
||||
import caHealthView from './ca/healthView.json';
|
||||
import caNotFoundView from './ca/notFoundView.json';
|
||||
@@ -81,6 +82,7 @@ import deHomeView from './de/homeView.json';
|
||||
import deLoginView from './de/loginView.json';
|
||||
import deGearsView from './de/gears/gearsView.json';
|
||||
import deGearView from './de/gears/gearView.json';
|
||||
import deStravaCallbackView from './de/strava/stravaCallbackView.json';
|
||||
import deActivityView from './de/activityView.json';
|
||||
import deHealthView from './de/healthView.json';
|
||||
import deNotFoundView from './de/notFoundView.json';
|
||||
@@ -125,6 +127,7 @@ import frHomeView from './fr/homeView.json';
|
||||
import frLoginView from './fr/loginView.json';
|
||||
import frGearsView from './fr/gears/gearsView.json';
|
||||
import frGearView from './fr/gears/gearView.json';
|
||||
import frStravaCallbackView from './fr/strava/stravaCallbackView.json';
|
||||
import frActivityView from './fr/activityView.json';
|
||||
import frHealthView from './fr/healthView.json';
|
||||
import frNotFoundView from './fr/notFoundView.json';
|
||||
@@ -169,6 +172,7 @@ import ptHomeView from './pt/homeView.json';
|
||||
import ptLoginView from './pt/loginView.json';
|
||||
import ptGearsView from './pt/gears/gearsView.json';
|
||||
import ptGearView from './pt/gears/gearView.json';
|
||||
import ptStravaCallbackView from './pt/strava/stravaCallbackView.json';
|
||||
import ptActivityView from './pt/activityView.json';
|
||||
import ptHealthView from './pt/healthView.json';
|
||||
import ptNotFoundView from './pt/notFoundView.json';
|
||||
@@ -212,6 +216,7 @@ import usHomeView from './us/homeView.json';
|
||||
import usLoginView from './us/loginView.json';
|
||||
import usGearsView from './us/gears/gearsView.json';
|
||||
import usGearView from './us/gears/gearView.json';
|
||||
import usStravaCallbackView from './us/strava/stravaCallbackView.json';
|
||||
import usActivityView from './us/activityView.json';
|
||||
import usHealthView from './us/healthView.json';
|
||||
import usNotFoundView from './us/notFoundView.json';
|
||||
@@ -257,6 +262,7 @@ const messages = {
|
||||
loginView: caLoginView,
|
||||
gearsView: caGearsView,
|
||||
gearView: caGearView,
|
||||
stravaCallbackView: caStravaCallbackView,
|
||||
activity: caActivityView,
|
||||
healthView: caHealthView,
|
||||
notFound: caNotFoundView,
|
||||
@@ -300,6 +306,7 @@ const messages = {
|
||||
loginView: deLoginView,
|
||||
gearsView: deGearsView,
|
||||
gearView: deGearView,
|
||||
stravaCallbackView: deStravaCallbackView,
|
||||
activity: deActivityView,
|
||||
healthView: deHealthView,
|
||||
notFound: deNotFoundView,
|
||||
@@ -343,6 +350,7 @@ const messages = {
|
||||
loginView: frLoginView,
|
||||
gearsView: frGearsView,
|
||||
gearView: frGearView,
|
||||
stravaCallbackView: frStravaCallbackView,
|
||||
activity: frActivityView,
|
||||
healthView: frHealthView,
|
||||
notFound: frNotFoundView,
|
||||
@@ -386,6 +394,7 @@ const messages = {
|
||||
loginView: ptLoginView,
|
||||
gearsView: ptGearsView,
|
||||
gearView: ptGearView,
|
||||
stravaCallbackView: ptStravaCallbackView,
|
||||
activity: ptActivityView,
|
||||
healthView: ptHealthView,
|
||||
notFound: ptNotFoundView,
|
||||
@@ -429,6 +438,7 @@ const messages = {
|
||||
loginView: usLoginView,
|
||||
gearsView: usGearsView,
|
||||
gearView: usGearView,
|
||||
stravaCallbackView: usStravaCallbackView,
|
||||
activity: usActivityView,
|
||||
healthView: usHealthView,
|
||||
notFound: usNotFoundView,
|
||||
|
||||
4
frontend/app/src/i18n/pt/strava/stravaCallbackView.json
Normal file
4
frontend/app/src/i18n/pt/strava/stravaCallbackView.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stravaCallbackViewTitle1": "A processar retorno da chamada de Strava",
|
||||
"stravaCallbackViewTitle2": "Por favor aguarde enquanto Strava está a ser ligado à sua conta. Não atualize esta página."
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"addEditUserModalEditTitle": "Edit user",
|
||||
"addEditUserModalEditProfileTitle": "Edit profile",
|
||||
"addEditUserModalAddTitle": "Add user",
|
||||
"addEditUserModalDeleteUserPhotoButton": "Delete photo",
|
||||
"addEditUserModalUserPhotoLabel": "User photo",
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"errorFetchingInfo": "Error fetching info",
|
||||
"errorEditingInfo": "Error editing info",
|
||||
"errorDeletingInfo": "Error deleting info",
|
||||
"errorUsernameAlreadyExistsFeedback": "Username already exists",
|
||||
"errorEmailNotValidFeedback": "Email not valid",
|
||||
"errorEmailAlreadyExistsFeedback": "Email already exists",
|
||||
"unitsCm": "cm",
|
||||
"unitsCms": "cms",
|
||||
"unitsInches": "inches",
|
||||
|
||||
4
frontend/app/src/i18n/us/strava/stravaCallbackView.json
Normal file
4
frontend/app/src/i18n/us/strava/stravaCallbackView.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"stravaCallbackViewTitle1": "Handling Strava callback",
|
||||
"stravaCallbackViewTitle2": "Please wait while Strava is being linked to your account. Do not refresh this page."
|
||||
}
|
||||
@@ -10,9 +10,15 @@ export const users = {
|
||||
getUserById(user_id) {
|
||||
return fetchGetRequest(`users/id/${user_id}`);
|
||||
},
|
||||
getUserByUsername(username){
|
||||
getUserContainsUsername(username){
|
||||
return fetchGetRequest(`users/username/contains/${username}`);
|
||||
},
|
||||
getUserByUsername(username){
|
||||
return fetchGetRequest(`users/username/${username}`);
|
||||
},
|
||||
getUserByEmail(email){
|
||||
return fetchGetRequest(`users/email/${email}`);
|
||||
},
|
||||
createUser(data) {
|
||||
return fetchPostRequest('users/create', data)
|
||||
},
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<LoadingComponent />
|
||||
<br>
|
||||
<p>{{ $t("stravaCallbackView.stravaCallbackViewTitle1") }}</p>
|
||||
<p>{{ $t("stravaCallbackView.stravaCallbackViewTitle2") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { onMounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
// Import Notivue push
|
||||
import { push } from "notivue";
|
||||
|
||||
Reference in New Issue
Block a user