mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-03 06:55:44 +00:00
feat: improve profile view with additional fields and validation
This commit is contained in:
@@ -32,6 +32,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Business contact field specific layouts */
|
||||
.mx_ExtendedProfileSettings_address_row {
|
||||
.mx_ExtendedProfileSettings_field_business_street {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_field_business_street_nr {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_postal_row {
|
||||
.mx_ExtendedProfileSettings_field_business_plz {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_field_business_city {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_contact_row {
|
||||
.mx_ExtendedProfileSettings_field_business_email {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_field_business_tel {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ExtendedProfileSettings_buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
@@ -18,7 +18,6 @@ import { SdkContextClass } from "../../../contexts/SDKContext";
|
||||
import { Container } from "../right_panel/UserInfo";
|
||||
import InlineSpinner from "../elements/InlineSpinner";
|
||||
import {
|
||||
EXT_PROFILE_KEY_ADDRESS,
|
||||
EXT_PROFILE_KEY_FURTHER_INFO,
|
||||
EXT_PROFILE_KEY_SPECIALIZATION,
|
||||
EXT_PROFILE_KEY_TITLE,
|
||||
@@ -61,13 +60,11 @@ const ExtendedProfileView: React.FC<ExtendedProfileViewProps> = ({ userId }) =>
|
||||
const [
|
||||
titleValue,
|
||||
specializationValue,
|
||||
addressValue,
|
||||
websiteValue,
|
||||
furtherInfoValue,
|
||||
] = await Promise.all([
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE),
|
||||
userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO),
|
||||
]);
|
||||
@@ -75,7 +72,6 @@ const ExtendedProfileView: React.FC<ExtendedProfileViewProps> = ({ userId }) =>
|
||||
const data = {
|
||||
title: titleValue as string | null,
|
||||
specialization: specializationValue as string | null,
|
||||
address: addressValue as string | null,
|
||||
website: websiteValue as string | null,
|
||||
furtherInfo: furtherInfoValue as string | null,
|
||||
};
|
||||
|
||||
@@ -15,7 +15,13 @@ import { _t } from "../../../languageHandler";
|
||||
import {
|
||||
EXT_PROFILE_KEY_TITLE,
|
||||
EXT_PROFILE_KEY_SPECIALIZATION,
|
||||
EXT_PROFILE_KEY_ADDRESS,
|
||||
EXT_PROFILE_KEY_PRACTICE_NAME,
|
||||
EXT_PROFILE_KEY_BUSINESS_STREET,
|
||||
EXT_PROFILE_KEY_BUSINESS_STREET_NR,
|
||||
EXT_PROFILE_KEY_BUSINESS_CITY,
|
||||
EXT_PROFILE_KEY_BUSINESS_PLZ,
|
||||
EXT_PROFILE_KEY_BUSINESS_EMAIL,
|
||||
EXT_PROFILE_KEY_BUSINESS_TEL,
|
||||
EXT_PROFILE_KEY_WEBSITE,
|
||||
EXT_PROFILE_KEY_FURTHER_INFO,
|
||||
} from "../settings/ExtendedProfileSettings";
|
||||
@@ -41,7 +47,13 @@ const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userI
|
||||
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [specialization, setSpecialization] = useState<string>("");
|
||||
const [address, setAddress] = useState<string>("");
|
||||
const [practiceName, setPracticeName] = useState<string>("");
|
||||
const [businessStreet, setBusinessStreet] = useState<string>("");
|
||||
const [businessStreetNr, setBusinessStreetNr] = useState<string>("");
|
||||
const [businessCity, setBusinessCity] = useState<string>("");
|
||||
const [businessPlz, setBusinessPlz] = useState<string>("");
|
||||
const [businessEmail, setBusinessEmail] = useState<string>("");
|
||||
const [businessTel, setBusinessTel] = useState<string>("");
|
||||
const [website, setWebsite] = useState<string>("");
|
||||
const [furtherInfo, setFurtherInfo] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
@@ -55,13 +67,25 @@ const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userI
|
||||
|
||||
const titleValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE);
|
||||
const specializationValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION);
|
||||
const addressValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS);
|
||||
const practiceNameValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_PRACTICE_NAME);
|
||||
const businessStreetValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_STREET);
|
||||
const businessStreetNrValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_STREET_NR);
|
||||
const businessCityValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_CITY);
|
||||
const businessPlzValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_PLZ);
|
||||
const businessEmailValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_EMAIL);
|
||||
const businessTelValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_TEL);
|
||||
const websiteValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE);
|
||||
const furtherInfoValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO);
|
||||
|
||||
setTitle(titleValue as string || "");
|
||||
setSpecialization(specializationValue as string || "");
|
||||
setAddress(addressValue as string || "");
|
||||
setPracticeName(practiceNameValue as string || "");
|
||||
setBusinessStreet(businessStreetValue as string || "");
|
||||
setBusinessStreetNr(businessStreetNrValue as string || "");
|
||||
setBusinessCity(businessCityValue as string || "");
|
||||
setBusinessPlz(businessPlzValue as string || "");
|
||||
setBusinessEmail(businessEmailValue as string || "");
|
||||
setBusinessTel(businessTelValue as string || "");
|
||||
setWebsite(websiteValue as string || "");
|
||||
setFurtherInfo(furtherInfoValue as string || "");
|
||||
} catch {
|
||||
@@ -83,15 +107,12 @@ const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userI
|
||||
}
|
||||
|
||||
// Don't render anything if there's no data
|
||||
if (!title && !specialization && !address && !website && !furtherInfo) {
|
||||
if (!title && !specialization && !practiceName && !businessStreet && !businessStreetNr && !businessCity && !businessPlz && !businessEmail && !businessTel && !website && !furtherInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_UserInfo_container mx_UserInfo_profileInfo">
|
||||
{/* <div className="mx_UserInfo_container_header">
|
||||
<Text weight="semibold">{_t("user_info|extended_profile|header")}</Text>
|
||||
</div> */}
|
||||
{title && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
@@ -116,7 +137,19 @@ const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userI
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{address && (
|
||||
{practiceName && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|practice_name")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">{practiceName}</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{businessStreet && businessCity && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
@@ -124,7 +157,39 @@ const UserExtendedProfileInfo: React.FC<UserExtendedProfileInfoProps> = ({ userI
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">{renderMultilineText(address)}</Text>
|
||||
<Text size="sm">
|
||||
{[businessStreet, businessStreetNr].filter(Boolean).join(" ")}
|
||||
{(businessStreet || businessStreetNr) && (businessPlz || businessCity) && ", "}
|
||||
{[businessPlz, businessCity].filter(Boolean).join(" ")}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{businessEmail && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|business_email")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">
|
||||
<a href={`mailto:${businessEmail}`}>{businessEmail}</a>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{businessTel && (
|
||||
<div className="mx_UserInfo_profileField">
|
||||
<div className="mx_UserInfo_fieldLabel">
|
||||
<Text size="sm" weight="semibold">
|
||||
{_t("user_info|extended_profile|business_tel")}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="mx_UserInfo_fieldValue">
|
||||
<Text size="sm">
|
||||
<a href={`tel:${businessTel}`}>{businessTel}</a>
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
|
||||
import React, { type ChangeEvent, useCallback, useEffect, useState } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Alert, ErrorMessage } from "@vector-im/compound-web";
|
||||
import { Alert } from "@vector-im/compound-web";
|
||||
import classNames from "classnames";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
@@ -28,9 +28,15 @@ const SpinnerToast: React.FC<{ children?: React.ReactNode }> = ({ children }) =>
|
||||
// Extended profile property keys
|
||||
export const EXT_PROFILE_KEY_TITLE = "io.element.profile.title";
|
||||
export const EXT_PROFILE_KEY_SPECIALIZATION = "io.element.profile.specialization";
|
||||
export const EXT_PROFILE_KEY_ADDRESS = "io.element.profile.address";
|
||||
export const EXT_PROFILE_KEY_WEBSITE = "io.element.profile.website";
|
||||
export const EXT_PROFILE_KEY_FURTHER_INFO = "io.element.profile.further_info";
|
||||
export const EXT_PROFILE_KEY_PRACTICE_NAME = "io.element.profile.practice_name";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_STREET = "io.element.profile.business_street";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_STREET_NR = "io.element.profile.business_street_nr";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_CITY = "io.element.profile.business_city";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_PLZ = "io.element.profile.business_plz";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_EMAIL = "io.element.profile.business_email";
|
||||
export const EXT_PROFILE_KEY_BUSINESS_TEL = "io.element.profile.business_tel";
|
||||
|
||||
// Unstable feature key for extended profiles
|
||||
export const UNSTABLE_MSC4133_EXTENDED_PROFILES = "org.matrix.msc4133.extended_profiles";
|
||||
@@ -41,21 +47,41 @@ interface ExtendedProfileSettingsProps {
|
||||
|
||||
/**
|
||||
* Component for managing extended profile fields for medical professionals.
|
||||
* Allows editing title, specialization, address, website, and further information.
|
||||
* Allows editing title, specialization, website, and further information.
|
||||
*/
|
||||
const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ className }) => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [specialization, setSpecialization] = useState<string>("");
|
||||
const [address, setAddress] = useState<string>("");
|
||||
const [website, setWebsite] = useState<string>("");
|
||||
const [furtherInfo, setFurtherInfo] = useState<string>("");
|
||||
const [practiceName, setPracticeName] = useState<string>("");
|
||||
|
||||
// Business contact field states
|
||||
const [businessStreet, setBusinessStreet] = useState<string>("");
|
||||
const [businessStreetNr, setBusinessStreetNr] = useState<string>("");
|
||||
const [businessCity, setBusinessCity] = useState<string>("");
|
||||
const [businessPlz, setBusinessPlz] = useState<string>("");
|
||||
const [businessEmail, setBusinessEmail] = useState<string>("");
|
||||
const [businessTel, setBusinessTel] = useState<string>("");
|
||||
|
||||
// Validation states for email and phone
|
||||
const [businessEmailValid, setBusinessEmailValid] = useState<boolean>(true);
|
||||
const [businessTelValid, setBusinessTelValid] = useState<boolean>(true);
|
||||
|
||||
// Store original values for cancel functionality
|
||||
const [originalTitle, setOriginalTitle] = useState<string>("");
|
||||
const [originalSpecialization, setOriginalSpecialization] = useState<string>("");
|
||||
const [originalAddress, setOriginalAddress] = useState<string>("");
|
||||
const [originalWebsite, setOriginalWebsite] = useState<string>("");
|
||||
const [originalFurtherInfo, setOriginalFurtherInfo] = useState<string>("");
|
||||
const [originalPracticeName, setOriginalPracticeName] = useState<string>("");
|
||||
|
||||
// Original business contact field values
|
||||
const [originalBusinessStreet, setOriginalBusinessStreet] = useState<string>("");
|
||||
const [originalBusinessStreetNr, setOriginalBusinessStreetNr] = useState<string>("");
|
||||
const [originalBusinessCity, setOriginalBusinessCity] = useState<string>("");
|
||||
const [originalBusinessPlz, setOriginalBusinessPlz] = useState<string>("");
|
||||
const [originalBusinessEmail, setOriginalBusinessEmail] = useState<string>("");
|
||||
const [originalBusinessTel, setOriginalBusinessTel] = useState<string>("");
|
||||
|
||||
const [isModified, setIsModified] = useState<boolean>(false);
|
||||
const [isSaving, setIsSaving] = useState<boolean>(false);
|
||||
@@ -76,28 +102,60 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
try {
|
||||
const titleValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE);
|
||||
const specializationValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION);
|
||||
const addressValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_ADDRESS);
|
||||
const websiteValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_WEBSITE);
|
||||
const furtherInfoValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO);
|
||||
const practiceNameValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_PRACTICE_NAME);
|
||||
|
||||
// Fetch business contact fields
|
||||
const businessStreetValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_STREET);
|
||||
const businessStreetNrValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_STREET_NR);
|
||||
const businessCityValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_CITY);
|
||||
const businessPlzValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_PLZ);
|
||||
const businessEmailValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_EMAIL);
|
||||
const businessTelValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_BUSINESS_TEL);
|
||||
|
||||
const titleStr = titleValue as string || "";
|
||||
const specializationStr = specializationValue as string || "";
|
||||
const addressStr = addressValue as string || "";
|
||||
const websiteStr = websiteValue as string || "";
|
||||
const furtherInfoStr = furtherInfoValue as string || "";
|
||||
const practiceNameStr = practiceNameValue as string || "";
|
||||
|
||||
// Convert business contact field values to strings
|
||||
const businessStreetStr = businessStreetValue as string || "";
|
||||
const businessStreetNrStr = businessStreetNrValue as string || "";
|
||||
const businessCityStr = businessCityValue as string || "";
|
||||
const businessPlzStr = businessPlzValue as string || "";
|
||||
const businessEmailStr = businessEmailValue as string || "";
|
||||
const businessTelStr = businessTelValue as string || "";
|
||||
|
||||
setTitle(titleStr);
|
||||
setSpecialization(specializationStr);
|
||||
setAddress(addressStr);
|
||||
setWebsite(websiteStr);
|
||||
setFurtherInfo(furtherInfoStr);
|
||||
setPracticeName(practiceNameStr);
|
||||
|
||||
// Set business contact fields
|
||||
setBusinessStreet(businessStreetStr);
|
||||
setBusinessStreetNr(businessStreetNrStr);
|
||||
setBusinessCity(businessCityStr);
|
||||
setBusinessPlz(businessPlzStr);
|
||||
setBusinessEmail(businessEmailStr);
|
||||
setBusinessTel(businessTelStr);
|
||||
|
||||
// Store original values for cancel functionality
|
||||
setOriginalTitle(titleStr);
|
||||
setOriginalSpecialization(specializationStr);
|
||||
setOriginalAddress(addressStr);
|
||||
setOriginalWebsite(websiteStr);
|
||||
setOriginalFurtherInfo(furtherInfoStr);
|
||||
setOriginalPracticeName(practiceNameStr);
|
||||
|
||||
// Store original business contact field values
|
||||
setOriginalBusinessStreet(businessStreetStr);
|
||||
setOriginalBusinessStreetNr(businessStreetNrStr);
|
||||
setOriginalBusinessCity(businessCityStr);
|
||||
setOriginalBusinessPlz(businessPlzStr);
|
||||
setOriginalBusinessEmail(businessEmailStr);
|
||||
setOriginalBusinessTel(businessTelStr);
|
||||
} catch (e) {
|
||||
// Just log the error but don't set loadError to true
|
||||
// as we want the fields to show up even when empty
|
||||
@@ -117,13 +175,24 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
const hasChanges =
|
||||
title !== originalTitle ||
|
||||
specialization !== originalSpecialization ||
|
||||
address !== originalAddress ||
|
||||
website !== originalWebsite ||
|
||||
furtherInfo !== originalFurtherInfo;
|
||||
furtherInfo !== originalFurtherInfo ||
|
||||
practiceName !== originalPracticeName ||
|
||||
businessStreet !== originalBusinessStreet ||
|
||||
businessStreetNr !== originalBusinessStreetNr ||
|
||||
businessCity !== originalBusinessCity ||
|
||||
businessPlz !== originalBusinessPlz ||
|
||||
businessEmail !== originalBusinessEmail ||
|
||||
businessTel !== originalBusinessTel;
|
||||
|
||||
setIsModified(hasChanges);
|
||||
}, [title, specialization, address, website, furtherInfo,
|
||||
originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo]);
|
||||
}, [
|
||||
title, specialization, website, furtherInfo, practiceName,
|
||||
originalTitle, originalSpecialization, originalWebsite, originalFurtherInfo, originalPracticeName,
|
||||
businessStreet, businessStreetNr, businessCity, businessPlz, businessEmail, businessTel,
|
||||
originalBusinessStreet, originalBusinessStreetNr, originalBusinessCity, originalBusinessPlz,
|
||||
originalBusinessEmail, originalBusinessTel
|
||||
]);
|
||||
|
||||
// Field change handlers
|
||||
const onTitleChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -134,10 +203,6 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
setSpecialization(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onAddressChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setAddress(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onWebsiteChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setWebsite(e.target.value);
|
||||
}, []);
|
||||
@@ -146,19 +211,80 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
setFurtherInfo(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onPracticeNameChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setPracticeName(e.target.value);
|
||||
}, []);
|
||||
|
||||
// Business contact field change handlers
|
||||
const onBusinessStreetChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBusinessStreet(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onBusinessStreetNrChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBusinessStreetNr(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onBusinessCityChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBusinessCity(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onBusinessPlzChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setBusinessPlz(e.target.value);
|
||||
}, []);
|
||||
|
||||
// Email validation with simple regex
|
||||
const onBusinessEmailChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setBusinessEmail(value);
|
||||
// Email format validation - basic check for something@something.something
|
||||
const emailValid = value === "" || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
setBusinessEmailValid(emailValid);
|
||||
}, []);
|
||||
|
||||
// Phone validation
|
||||
const onBusinessTelChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setBusinessTel(value);
|
||||
// Phone format validation - allow digits, spaces, plus, minus, parentheses
|
||||
const phoneValid = value === "" || /^[0-9+\-() ]+$/.test(value);
|
||||
setBusinessTelValid(phoneValid);
|
||||
}, []);
|
||||
|
||||
// Cancel handler - restore all original values
|
||||
const onCancel = useCallback(() => {
|
||||
setTitle(originalTitle);
|
||||
setSpecialization(originalSpecialization);
|
||||
setAddress(originalAddress);
|
||||
setWebsite(originalWebsite);
|
||||
setFurtherInfo(originalFurtherInfo);
|
||||
}, [originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo]);
|
||||
setPracticeName(originalPracticeName);
|
||||
|
||||
// Reset business contact fields to original values
|
||||
setBusinessStreet(originalBusinessStreet);
|
||||
setBusinessStreetNr(originalBusinessStreetNr);
|
||||
setBusinessCity(originalBusinessCity);
|
||||
setBusinessPlz(originalBusinessPlz);
|
||||
setBusinessEmail(originalBusinessEmail);
|
||||
setBusinessTel(originalBusinessTel);
|
||||
|
||||
// Reset validation states
|
||||
setBusinessEmailValid(true);
|
||||
setBusinessTelValid(true);
|
||||
}, [
|
||||
originalTitle, originalSpecialization, originalWebsite, originalFurtherInfo, originalPracticeName,
|
||||
originalBusinessStreet, originalBusinessStreetNr, originalBusinessCity, originalBusinessPlz,
|
||||
originalBusinessEmail, originalBusinessTel
|
||||
]);
|
||||
|
||||
// Save handler - save all fields at once
|
||||
const onSave = useCallback(async (): Promise<void> => {
|
||||
if (!isModified) return;
|
||||
|
||||
// Validate email and phone fields before saving
|
||||
if (!businessEmailValid || !businessTelValid) {
|
||||
setSaveError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
setSaveError(false);
|
||||
const removeToast = toastRack.displayToast(
|
||||
@@ -177,10 +303,6 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_SPECIALIZATION, specialization));
|
||||
}
|
||||
|
||||
if (address !== originalAddress) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_ADDRESS, address));
|
||||
}
|
||||
|
||||
if (website !== originalWebsite) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_WEBSITE, website));
|
||||
}
|
||||
@@ -189,14 +311,51 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_FURTHER_INFO, furtherInfo));
|
||||
}
|
||||
|
||||
if (practiceName !== originalPracticeName) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_PRACTICE_NAME, practiceName));
|
||||
}
|
||||
|
||||
// Save business contact fields if modified
|
||||
if (businessStreet !== originalBusinessStreet) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_STREET, businessStreet));
|
||||
}
|
||||
|
||||
if (businessStreetNr !== originalBusinessStreetNr) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_STREET_NR, businessStreetNr));
|
||||
}
|
||||
|
||||
if (businessCity !== originalBusinessCity) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_CITY, businessCity));
|
||||
}
|
||||
|
||||
if (businessPlz !== originalBusinessPlz) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_PLZ, businessPlz));
|
||||
}
|
||||
|
||||
if (businessEmail !== originalBusinessEmail) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_EMAIL, businessEmail));
|
||||
}
|
||||
|
||||
if (businessTel !== originalBusinessTel) {
|
||||
savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_BUSINESS_TEL, businessTel));
|
||||
}
|
||||
|
||||
await Promise.all(savePromises);
|
||||
|
||||
// Update original values after successful save
|
||||
setOriginalTitle(title);
|
||||
setOriginalSpecialization(specialization);
|
||||
setOriginalAddress(address);
|
||||
setOriginalWebsite(website);
|
||||
setOriginalFurtherInfo(furtherInfo);
|
||||
setOriginalPracticeName(practiceName);
|
||||
|
||||
// Update original business contact field values
|
||||
setOriginalBusinessStreet(businessStreet);
|
||||
setOriginalBusinessStreetNr(businessStreetNr);
|
||||
setOriginalBusinessCity(businessCity);
|
||||
setOriginalBusinessPlz(businessPlz);
|
||||
setOriginalBusinessEmail(businessEmail);
|
||||
setOriginalBusinessTel(businessTel);
|
||||
|
||||
} catch (e) {
|
||||
logger.error("Failed to save profile information", e);
|
||||
@@ -207,8 +366,11 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
removeToast();
|
||||
}
|
||||
}, [
|
||||
isModified, title, specialization, address, website, furtherInfo,
|
||||
originalTitle, originalSpecialization, originalAddress, originalWebsite, originalFurtherInfo,
|
||||
isModified, title, specialization, website, furtherInfo, practiceName,
|
||||
originalTitle, originalSpecialization, originalWebsite, originalFurtherInfo, originalPracticeName,
|
||||
businessStreet, businessStreetNr, businessCity, businessPlz, businessEmail, businessTel,
|
||||
originalBusinessStreet, originalBusinessStreetNr, originalBusinessCity, originalBusinessPlz,
|
||||
originalBusinessEmail, originalBusinessTel, businessEmailValid, businessTelValid,
|
||||
toastRack, userProfilesStore
|
||||
]);
|
||||
|
||||
@@ -248,14 +410,85 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|address")}
|
||||
value={address}
|
||||
onChange={onAddressChanged}
|
||||
label={_t("settings|extended_profile_settings|practice_name")}
|
||||
value={practiceName}
|
||||
onChange={onPracticeNameChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Street and number in one row, street takes 2/3 width */}
|
||||
<div className="mx_ExtendedProfileSettings_fieldsRow mx_ExtendedProfileSettings_address_row">
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_business_street">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_street")}
|
||||
value={businessStreet}
|
||||
onChange={onBusinessStreetChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_business_street_nr">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_street_nr")}
|
||||
value={businessStreetNr}
|
||||
onChange={onBusinessStreetNrChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PLZ and City in one row, PLZ takes 1/3 width */}
|
||||
<div className="mx_ExtendedProfileSettings_fieldsRow mx_ExtendedProfileSettings_postal_row">
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_business_plz">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_plz")}
|
||||
value={businessPlz}
|
||||
onChange={onBusinessPlzChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_business_city">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_city")}
|
||||
value={businessCity}
|
||||
onChange={onBusinessCityChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email field with validation */}
|
||||
<div className="mx_ExtendedProfileSettings_fieldsRow mx_ExtendedProfileSettings_contact_row">
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_email")}
|
||||
value={businessEmail}
|
||||
onChange={onBusinessEmailChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
type="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Telephone field with validation */}
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|business_tel")}
|
||||
value={businessTel}
|
||||
onChange={onBusinessTelChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
type="tel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|website")}
|
||||
@@ -288,11 +521,12 @@ const ExtendedProfileSettings: React.FC<ExtendedProfileSettingsProps> = ({ class
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{saveError && (
|
||||
<div className="mx_ExtendedProfileSettings_error">
|
||||
<ErrorMessage>{_t("settings|extended_profile_settings|failed_to_save")}</ErrorMessage>
|
||||
</div>
|
||||
<Alert title={_t("settings|extended_profile_settings|failed_to_save")} type="critical">
|
||||
{businessEmailValid ? null : _t("settings|extended_profile_settings|invalid_email")}
|
||||
<span> </span>
|
||||
{businessTelValid ? null : _t("settings|extended_profile_settings|invalid_phone")}
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2479,13 +2479,21 @@
|
||||
"additional_information": "Zusätzliche Informationen",
|
||||
"professional_title": "Titel",
|
||||
"specialization": "Fachgebiet",
|
||||
"address": "Adresse",
|
||||
"practice_name": "Praxisname / Firmenname",
|
||||
"business_street": "Strasse",
|
||||
"business_street_nr": "Hausnummer",
|
||||
"business_plz": "PLZ",
|
||||
"business_city": "Ort",
|
||||
"business_email": "Email (geschäftlich)",
|
||||
"business_tel": "Telefon (geschäftlich)",
|
||||
"website": "Webseite",
|
||||
"further_information": "Weitere Informationen / Qualifikationen",
|
||||
"saving_profile": "Profil wird gespeichert...",
|
||||
"failed_to_load": "Fehler beim Laden der erweiterten Profileinstellungen.",
|
||||
"failed_to_save": "Fehler beim Speichern der erweiterten Profileinstellungen.",
|
||||
"server_not_supported": "Dieser Server unterstützt keine erweiterten Profileinstellungen."
|
||||
"server_not_supported": "Dieser Server unterstützt keine erweiterten Profileinstellungen.",
|
||||
"invalid_email": "Ungültige E-Mail-Adresse.",
|
||||
"invalid_phone": "Ungültige Telefonnummer."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Einstellungen:</strong> Konto",
|
||||
@@ -3832,7 +3840,10 @@
|
||||
"header": "Weitere Informationen",
|
||||
"title": "Titel",
|
||||
"specialization": "Fachgebiet",
|
||||
"practice_name": "Praxisname / Firmenname",
|
||||
"address": "Adresse",
|
||||
"business_email": "E-Mail (geschäftlich)",
|
||||
"business_tel": "Telefon (geschäftlich)",
|
||||
"website": "Webseite",
|
||||
"further_info": "Weitere Infos / Qualifikationen",
|
||||
"not_provided": "Nicht angegeben",
|
||||
|
||||
@@ -2512,13 +2512,21 @@
|
||||
"additional_information": "Additional Information",
|
||||
"professional_title": "Title",
|
||||
"specialization": "Specialization",
|
||||
"address": "Address",
|
||||
"practice_name": "Practice Name",
|
||||
"business_street": "Strasse",
|
||||
"business_street_nr": "Street Number",
|
||||
"business_plz": "ZIP Code",
|
||||
"business_city": "City",
|
||||
"business_email": "Business Email",
|
||||
"business_tel": "Business Phone",
|
||||
"website": "Website",
|
||||
"further_information": "Further Information / Qualifications",
|
||||
"saving_profile": "Saving profile...",
|
||||
"failed_to_load": "Failed to load extended profile settings.",
|
||||
"failed_to_save": "Failed to save extended profile settings.",
|
||||
"server_not_supported": "Your server does not support extended profile settings."
|
||||
"server_not_supported": "Your server does not support extended profile settings.",
|
||||
"invalid_email": "Invalid email.",
|
||||
"invalid_phone": "Invalid phone number."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Settings:</strong> Account",
|
||||
@@ -3875,7 +3883,10 @@
|
||||
"header": "Additional infos",
|
||||
"title": "Title",
|
||||
"specialization": "Speciality",
|
||||
"practice_name": "Practice name",
|
||||
"address": "Address",
|
||||
"business_email": "Business email",
|
||||
"business_tel": "Business phone",
|
||||
"website": "Website",
|
||||
"further_info": "Further infos / qualifications",
|
||||
"not_provided": "Not provided",
|
||||
|
||||
@@ -2507,13 +2507,21 @@
|
||||
"additional_information": "Informations supplémentaires",
|
||||
"professional_title": "Titre",
|
||||
"specialization": "Spécialisation",
|
||||
"address": "Adresse",
|
||||
"practice_name": "Nom de l’entreprise",
|
||||
"business_street": "Rue",
|
||||
"business_street_nr": "Numéro de rue",
|
||||
"business_plz": "Code postal",
|
||||
"business_city": "Ville",
|
||||
"business_email": "E-mail professionnel",
|
||||
"business_tel": "Téléphone professionnel",
|
||||
"website": "Site web",
|
||||
"further_information": "Informations supplémentaires / qualifications",
|
||||
"saving_profile": "Enregistrement des paramètres de profil étendus…",
|
||||
"failed_to_load": "Erreur lors du chargement des paramètres de profil étendus.",
|
||||
"failed_to_save": "Erreur lors de l'enregistrement des paramètres de profil étendus.",
|
||||
"server_not_supported": "Le serveur d’accueil ne prend pas en charge les paramètres de profil étendus."
|
||||
"server_not_supported": "Le serveur d’accueil ne prend pas en charge les paramètres de profil étendus.",
|
||||
"invalid_email": "Adresse e-mail invalide.",
|
||||
"invalid_phone": "Numéro de téléphone invalide."
|
||||
},
|
||||
"account": {
|
||||
"dialog_title": "<strong>Paramètres : </strong> Compte",
|
||||
@@ -3869,7 +3877,10 @@
|
||||
"header": "Informations supplémentaires",
|
||||
"title": "Titre",
|
||||
"specialization": "Spécialisation",
|
||||
"practice_name": "Nom de l’entreprise",
|
||||
"address": "Adresse",
|
||||
"business_email": "Email (professionnel)",
|
||||
"business_tel": "Téléphone (professionnel)",
|
||||
"website": "Site web",
|
||||
"further_info": "Informations supplémentaires / qualifications",
|
||||
"not_provided": "Non fourni",
|
||||
|
||||
Reference in New Issue
Block a user