feat: improve profile view with additional fields and validation

This commit is contained in:
2025-04-11 20:32:22 +02:00
parent ab653d1f0e
commit d038c0aafc
7 changed files with 412 additions and 53 deletions
@@ -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>
+13 -2
View File
@@ -2562,13 +2562,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",
@@ -3937,7 +3945,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",
+13 -2
View File
@@ -2580,13 +2580,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",
@@ -3956,7 +3964,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",
+13 -2
View File
@@ -2567,13 +2567,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",
@@ -3942,7 +3950,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",