diff --git a/apps/web/res/css/_components.pcss b/apps/web/res/css/_components.pcss index 6dfb7259fa..d3eeb76702 100644 --- a/apps/web/res/css/_components.pcss +++ b/apps/web/res/css/_components.pcss @@ -235,7 +235,6 @@ @import "./views/right_panel/_BaseCard.pcss"; @import "./views/right_panel/_EmptyState.pcss"; @import "./views/right_panel/_EncryptionInfo.pcss"; -@import "./views/right_panel/_ExtendedProfileView.pcss"; @import "./views/right_panel/_ExtensionsCard.pcss"; @import "./views/right_panel/_PinnedMessagesCard.pcss"; @import "./views/right_panel/_RoomSummaryCard.pcss"; @@ -304,7 +303,6 @@ @import "./views/rooms/wysiwyg_composer/components/_FormattingButtons.pcss"; @import "./views/rooms/wysiwyg_composer/components/_LinkModal.pcss"; @import "./views/settings/_AvatarSetting.pcss"; -@import "./views/settings/_ExtendedProfileSettings.pcss"; @import "./views/settings/_FontScalingPanel.pcss"; @import "./views/settings/_ImageSizePanel.pcss"; @import "./views/settings/_IntegrationManager.pcss"; diff --git a/apps/web/res/css/views/right_panel/_ExtendedProfileView.pcss b/apps/web/res/css/views/right_panel/_ExtendedProfileView.pcss deleted file mode 100644 index faa75bc877..0000000000 --- a/apps/web/res/css/views/right_panel/_ExtendedProfileView.pcss +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_ExtendedProfileView { - /* Anpassung an das bestehende UserInfo Panel */ - font-size: var(--cpd-font-size-body-sm); - padding: 0; - - /* Styling für das Accordion */ - .mx_ExtendedProfileView_accordion { - width: 100%; - } - - /* Abstand zwischen den Feldern */ - .mx_ExtendedProfileView_content { - padding: var(--cpd-space-2x) 0; - display: flex; - flex-direction: column; - gap: var(--cpd-space-4x); - } - - /* Styling für die Felder */ - .mx_ExtendedProfileView_field { - margin-bottom: var(--cpd-space-2x); - - .mx_ExtendedProfileView_icon { - width: 20px; - height: 20px; - color: var(--cpd-color-icon-secondary); - flex-shrink: 0; - } - } - - /* Labels und Werte */ - .mx_ExtendedProfileView_label { - color: var(--cpd-color-text-secondary); - font-size: var(--cpd-font-size-body-xs); - margin-bottom: var(--cpd-space-1x); - } - - .mx_ExtendedProfileView_value { - color: var(--cpd-color-text-primary); - word-break: break-word; - - &.mx_ExtendedProfileView_multiline { - white-space: pre-line; - margin-top: var(--cpd-space-1x); - } - } - - /* Spezielle Styles für Website-Links */ - .mx_ExtendedProfileView_link { - color: var(--cpd-color-text-action-accent); - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - - /* Für Bereich "Weitere Informationen" */ - .mx_ExtendedProfileView_further_info { - padding: var(--cpd-space-1x) 0; - } - - /* Loading State */ - .mx_ExtendedProfileView_loading { - padding: var(--cpd-space-4x) 0; - } - - /* Error State */ - .mx_ExtendedProfileView_error { - margin: var(--cpd-space-2x) 0; - } -} diff --git a/apps/web/res/css/views/settings/_ExtendedProfileSettings.pcss b/apps/web/res/css/views/settings/_ExtendedProfileSettings.pcss deleted file mode 100644 index c0080e5901..0000000000 --- a/apps/web/res/css/views/settings/_ExtendedProfileSettings.pcss +++ /dev/null @@ -1,76 +0,0 @@ -.mx_ExtendedProfileSettings { - margin-top: 24px; - margin-bottom: 16px; -} - -.mx_ExtendedProfileSettings h3 { - font-size: 16px; - font-weight: 600; - margin-top: 0; - margin-bottom: 16px; -} - -.mx_ExtendedProfileSettings_fields { - display: flex; - flex-direction: column; - gap: 16px; -} - -.mx_ExtendedProfileSettings_field { - width: 100%; - margin-bottom: 20px; -} - -.mx_ExtendedProfileSettings_fieldsRow { - display: flex; - gap: 16px; - margin-bottom: 20px; - - .mx_ExtendedProfileSettings_field { - flex: 1; - margin-bottom: 0; - } -} - -/* 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; - justify-content: flex-end; - margin-top: 8px; -} - -.mx_ExtendedProfileSettings_multiline textarea { - min-height: 200px; - resize: vertical; -} \ No newline at end of file diff --git a/apps/web/src/components/views/right_panel/ExtendedProfileView.tsx b/apps/web/src/components/views/right_panel/ExtendedProfileView.tsx deleted file mode 100644 index 9c0fa7d380..0000000000 --- a/apps/web/src/components/views/right_panel/ExtendedProfileView.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -import React, { useCallback, useEffect, useState } from "react"; -import { logger } from "matrix-js-sdk/src/logger"; -import { Alert } from "@vector-im/compound-web"; -import GlobeIcon from "@vector-im/compound-design-tokens/assets/web/icons/web-browser"; -import LocationIcon from "@vector-im/compound-design-tokens/assets/web/icons/location-pin"; -import BadgeIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-profile"; -import StarIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite"; - -import { _t } from "../../../languageHandler"; -import { SdkContextClass } from "../../../contexts/SDKContext"; -import { Container } from "../right_panel/UserInfo"; -import InlineSpinner from "../elements/InlineSpinner"; -import { - EXT_PROFILE_KEY_FURTHER_INFO, - EXT_PROFILE_KEY_SPECIALIZATION, - EXT_PROFILE_KEY_TITLE, - EXT_PROFILE_KEY_WEBSITE, -} from "../settings/ExtendedProfileSettings"; -import { Flex } from "@element-hq/web-shared-components"; - -interface ExtendedProfileViewProps { - userId: string; -} - -type ExtendedProfileData = { - title: string | null; - specialization: string | null; - address: string | null; - website: string | null; - furtherInfo: string | null; -}; - -/** - * Komponente zur Anzeige erweiterter Profilfelder eines anderen Benutzers - */ -const ExtendedProfileView: React.FC = ({ userId }) => { - const [loading, setLoading] = useState(true); - const [error, setError] = useState(false); - const [profileData, setProfileData] = useState(null); - const [hasData, setHasData] = useState(false); - - //const client = useMatrixClientContext(); - const userProfilesStore = SdkContextClass.instance.userProfilesStore; - - // Laden der erweiterten Profilfelder für den angegebenen Benutzer - const fetchExtendedProfileData = useCallback(async () => { - if (!userId) return; - - setLoading(true); - setError(false); - - try { - const [ - titleValue, - specializationValue, - 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_WEBSITE), - userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_FURTHER_INFO), - ]); - - const data: ExtendedProfileData = { - title: titleValue as string | null, - specialization: specializationValue as string | null, - address: null, // Address not yet implemented - website: websiteValue as string | null, - furtherInfo: furtherInfoValue as string | null, - }; - - setProfileData(data); - - // Prüfen, ob mindestens ein Feld ausgefüllt ist - const hasAnyData = Object.values(data).some((value) => value !== null && value !== ""); - setHasData(hasAnyData); - } catch (e) { - logger.error("Fehler beim Laden der erweiterten Profildaten", e); - setError(true); - } finally { - setLoading(false); - } - }, [userId, userProfilesStore]); - - useEffect(() => { - fetchExtendedProfileData(); - }, [fetchExtendedProfileData]); - - // Wenn keine Daten vorhanden sind, zeigen wir nichts an - if (!loading && !hasData && !error) { - return null; - } - - return ( - - {loading ? ( - - - - ) : error ? ( - - {_t("settings|extended_profile_settings|failed_to_load")} - - ) : hasData ? ( -
- {(profileData?.title || profileData?.specialization) && ( -
- {profileData.title && ( -
- - -
-
{_t("settings|extended_profile_settings|professional_title")}
-
{profileData.title}
-
-
-
- )} - - {profileData.specialization && ( -
- - -
-
{_t("settings|extended_profile_settings|specialization")}
-
{profileData.specialization}
-
-
-
- )} -
- )} - - {profileData?.address && ( -
- - -
-
{_t("settings|extended_profile_settings|business_city")}
-
{profileData.address}
-
-
-
- )} - - {profileData?.website && ( -
- - -
-
{_t("settings|extended_profile_settings|website")}
- -
-
-
- )} - - {profileData?.furtherInfo && ( -
-
-
{_t("settings|extended_profile_settings|further_information")}
-
{profileData.furtherInfo}
-
-
- )} -
- ) : null} -
- ); -}; - -export default ExtendedProfileView; diff --git a/apps/web/src/components/views/right_panel/UserExtendedProfileInfo.tsx b/apps/web/src/components/views/right_panel/UserExtendedProfileInfo.tsx index 484e7c7941..7dfaf4ddfb 100644 --- a/apps/web/src/components/views/right_panel/UserExtendedProfileInfo.tsx +++ b/apps/web/src/components/views/right_panel/UserExtendedProfileInfo.tsx @@ -11,20 +11,19 @@ import { Text } from "@vector-im/compound-web"; import { SdkContextClass } from "../../../contexts/SDKContext"; import Spinner from "../elements/Spinner"; import { _t } from "../../../languageHandler"; -// Extended profile property keys -import { - EXT_PROFILE_KEY_TITLE, - EXT_PROFILE_KEY_SPECIALIZATION, - 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"; + +// Extended profile property keys (read-only display in the right panel). +const EXT_PROFILE_KEY_TITLE = "io.element.profile.title"; +const EXT_PROFILE_KEY_SPECIALIZATION = "io.element.profile.specialization"; +const EXT_PROFILE_KEY_WEBSITE = "io.element.profile.website"; +const EXT_PROFILE_KEY_FURTHER_INFO = "io.element.profile.further_info"; +const EXT_PROFILE_KEY_PRACTICE_NAME = "io.element.profile.practice_name"; +const EXT_PROFILE_KEY_BUSINESS_STREET = "io.element.profile.business_street"; +const EXT_PROFILE_KEY_BUSINESS_STREET_NR = "io.element.profile.business_street_nr"; +const EXT_PROFILE_KEY_BUSINESS_CITY = "io.element.profile.business_city"; +const EXT_PROFILE_KEY_BUSINESS_PLZ = "io.element.profile.business_plz"; +const EXT_PROFILE_KEY_BUSINESS_EMAIL = "io.element.profile.business_email"; +const EXT_PROFILE_KEY_BUSINESS_TEL = "io.element.profile.business_tel"; // Helper function to preserve line breaks const renderMultilineText = (text: string): React.ReactNode[] => { @@ -60,7 +59,7 @@ const UserExtendedProfileInfo: React.FC = ({ userI const [error, setError] = useState(false); useEffect(() => { - const fetchProfileData = async () => { + const fetchProfileData = async (): Promise => { try { setError(false); setLoading(true); diff --git a/apps/web/src/components/views/settings/ExtendedProfileSettings.tsx b/apps/web/src/components/views/settings/ExtendedProfileSettings.tsx deleted file mode 100644 index 527315ed17..0000000000 --- a/apps/web/src/components/views/settings/ExtendedProfileSettings.tsx +++ /dev/null @@ -1,536 +0,0 @@ -/* -Copyright 2025 New Vector Ltd. - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -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 } from "@vector-im/compound-web"; -import classNames from "classnames"; - -import { _t } from "../../../languageHandler"; -import { useToastContext } from "../../../contexts/ToastContext"; -import InlineSpinner from "../elements/InlineSpinner"; -import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -import { SdkContextClass } from "../../../contexts/SDKContext"; -import Field from "../elements/Field"; -import AccessibleButton from "../elements/AccessibleButton"; - -const SpinnerToast: React.FC<{ children?: React.ReactNode }> = ({ children }) => ( - <> - - {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_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"; - -interface ExtendedProfileSettingsProps { - className?: string; -} - -/** - * Component for managing extended profile fields for medical professionals. - * Allows editing title, specialization, website, and further information. - */ -const ExtendedProfileSettings: React.FC = ({ className }) => { - const [title, setTitle] = useState(""); - const [specialization, setSpecialization] = useState(""); - const [website, setWebsite] = useState(""); - const [furtherInfo, setFurtherInfo] = useState(""); - const [practiceName, setPracticeName] = useState(""); - - // Business contact field states - const [businessStreet, setBusinessStreet] = useState(""); - const [businessStreetNr, setBusinessStreetNr] = useState(""); - const [businessCity, setBusinessCity] = useState(""); - const [businessPlz, setBusinessPlz] = useState(""); - const [businessEmail, setBusinessEmail] = useState(""); - const [businessTel, setBusinessTel] = useState(""); - - // Validation states for email and phone - const [businessEmailValid, setBusinessEmailValid] = useState(true); - const [businessTelValid, setBusinessTelValid] = useState(true); - - // Store original values for cancel functionality - const [originalTitle, setOriginalTitle] = useState(""); - const [originalSpecialization, setOriginalSpecialization] = useState(""); - const [originalWebsite, setOriginalWebsite] = useState(""); - const [originalFurtherInfo, setOriginalFurtherInfo] = useState(""); - const [originalPracticeName, setOriginalPracticeName] = useState(""); - - // Original business contact field values - const [originalBusinessStreet, setOriginalBusinessStreet] = useState(""); - const [originalBusinessStreetNr, setOriginalBusinessStreetNr] = useState(""); - const [originalBusinessCity, setOriginalBusinessCity] = useState(""); - const [originalBusinessPlz, setOriginalBusinessPlz] = useState(""); - const [originalBusinessEmail, setOriginalBusinessEmail] = useState(""); - const [originalBusinessTel, setOriginalBusinessTel] = useState(""); - - const [isModified, setIsModified] = useState(false); - const [isSaving, setIsSaving] = useState(false); - const [saveError, setSaveError] = useState(false); - const [loadError, setLoadError] = useState(false); - - const toastRack = useToastContext(); - const client = useMatrixClientContext(); - const userProfilesStore = SdkContextClass.instance.userProfilesStore; - const userId = client.getSafeUserId(); - - // Load extended profile data without checking server support - useEffect(() => { - const loadExtendedProfileData = async () => { - try { - setLoadError(false); - // Try to get existing data - try { - const titleValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_TITLE); - const specializationValue = await userProfilesStore.getOrFetchExtendedProfileProperty(userId, EXT_PROFILE_KEY_SPECIALIZATION); - 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 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); - 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); - 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 - logger.warn("Failed to fetch extended profile properties", e); - } - } catch (e) { - logger.error("Failed to load extended profile data", e); - setLoadError(true); - } - }; - - loadExtendedProfileData(); - }, [userId, userProfilesStore, client]); - - // Check if any field has been modified - useEffect(() => { - const hasChanges = - title !== originalTitle || - specialization !== originalSpecialization || - website !== originalWebsite || - furtherInfo !== originalFurtherInfo || - practiceName !== originalPracticeName || - businessStreet !== originalBusinessStreet || - businessStreetNr !== originalBusinessStreetNr || - businessCity !== originalBusinessCity || - businessPlz !== originalBusinessPlz || - businessEmail !== originalBusinessEmail || - businessTel !== originalBusinessTel; - - setIsModified(hasChanges); - }, [ - 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) => { - setTitle(e.target.value); - }, []); - - const onSpecializationChanged = useCallback((e: ChangeEvent) => { - setSpecialization(e.target.value); - }, []); - - const onWebsiteChanged = useCallback((e: ChangeEvent) => { - setWebsite(e.target.value); - }, []); - - const onFurtherInfoChanged = useCallback((e: ChangeEvent) => { - setFurtherInfo(e.target.value); - }, []); - - const onPracticeNameChanged = useCallback((e: ChangeEvent) => { - setPracticeName(e.target.value); - }, []); - - // Business contact field change handlers - const onBusinessStreetChanged = useCallback((e: ChangeEvent) => { - setBusinessStreet(e.target.value); - }, []); - - const onBusinessStreetNrChanged = useCallback((e: ChangeEvent) => { - setBusinessStreetNr(e.target.value); - }, []); - - const onBusinessCityChanged = useCallback((e: ChangeEvent) => { - setBusinessCity(e.target.value); - }, []); - - const onBusinessPlzChanged = useCallback((e: ChangeEvent) => { - setBusinessPlz(e.target.value); - }, []); - - // Email validation with simple regex - const onBusinessEmailChanged = useCallback((e: ChangeEvent) => { - 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) => { - 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); - setWebsite(originalWebsite); - setFurtherInfo(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 => { - if (!isModified) return; - - // Validate email and phone fields before saving - if (!businessEmailValid || !businessTelValid) { - setSaveError(true); - return; - } - - setIsSaving(true); - setSaveError(false); - const removeToast = toastRack.displayToast( - {_t("settings|extended_profile_settings|saving_profile")} - ); - - try { - // Save all modified fields - const savePromises = []; - - if (title !== originalTitle) { - savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_TITLE, title)); - } - - if (specialization !== originalSpecialization) { - savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_SPECIALIZATION, specialization)); - } - - if (website !== originalWebsite) { - savePromises.push(userProfilesStore.setExtendedProfileProperty(EXT_PROFILE_KEY_WEBSITE, website)); - } - - if (furtherInfo !== originalFurtherInfo) { - 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); - 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); - setSaveError(true); - throw e; - } finally { - setIsSaving(false); - removeToast(); - } - }, [ - 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 - ]); - - return ( -
-
-

{_t("settings|extended_profile_settings|additional_information")}

- - {loadError && ( - - {_t("settings|extended_profile_settings|failed_to_load")} - - )} - -
-
-
- -
- -
- -
-
- -
- -
- - {/* Street and number in one row, street takes 2/3 width */} -
-
- -
-
- -
-
- - {/* PLZ and City in one row, PLZ takes 1/3 width */} -
-
- -
-
- -
-
- - {/* Email field with validation */} -
-
- -
- - {/* Telephone field with validation */} -
- -
-
- - -
- -
- -
- -
- - {/* Single set of buttons for all fields */} -
- - {_t("common|cancel")} - - - {_t("common|save")} - -
-
- {saveError && ( - - {businessEmailValid ? null : _t("settings|extended_profile_settings|invalid_email")} - - {businessTelValid ? null : _t("settings|extended_profile_settings|invalid_phone")} - - )} -
-
- ); -}; - -export default ExtendedProfileSettings; diff --git a/apps/web/src/components/views/settings/UserProfileSettings.tsx b/apps/web/src/components/views/settings/UserProfileSettings.tsx index d18dce332c..9f3d66b328 100644 --- a/apps/web/src/components/views/settings/UserProfileSettings.tsx +++ b/apps/web/src/components/views/settings/UserProfileSettings.tsx @@ -28,7 +28,6 @@ import LogoutDialog, { shouldShowLogoutDialog } from "../dialogs/LogoutDialog"; import Modal from "../../../Modal"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { SettingsSection } from "./shared/SettingsSection.tsx"; -import ExtendedProfileSettings from "./ExtendedProfileSettings"; const SpinnerToast: React.FC<{ children?: ReactNode }> = ({ children }) => ( <> @@ -239,9 +238,6 @@ const UserProfileSettings: React.FC = ({ )} {userIdentifier && } - {/* Always display the Extended Profile Settings component */} - - {externalAccountManagementUrl && ( diff --git a/apps/web/src/i18n/strings/de_DE.json b/apps/web/src/i18n/strings/de_DE.json index 2ad9fa82c5..11ddbeb62b 100644 --- a/apps/web/src/i18n/strings/de_DE.json +++ b/apps/web/src/i18n/strings/de_DE.json @@ -2441,26 +2441,6 @@ } }, "settings": { - "extended_profile_settings": { - "additional_information": "Zusätzliche Informationen", - "professional_title": "Titel", - "specialization": "Fachgebiet", - "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.", - "invalid_email": "Ungültige E-Mail-Adresse.", - "invalid_phone": "Ungültige Telefonnummer." - }, "account": { "dialog_title": "Einstellungen: Konto", "title": "Konto" @@ -3760,7 +3740,6 @@ }, "user_info": { "extended_profile": { - "header": "Weitere Informationen", "title": "Titel", "specialization": "Fachgebiet", "practice_name": "Praxisname / Firmenname", @@ -3769,7 +3748,6 @@ "business_tel": "Telefon (geschäftlich)", "website": "Webseite", "further_info": "Weitere Infos / Qualifikationen", - "not_provided": "Nicht angegeben", "error_loading_profile": "Fehler beim Laden des Profils" }, "admin_tools_section": "Administrationswerkzeuge", diff --git a/apps/web/src/i18n/strings/en_EN.json b/apps/web/src/i18n/strings/en_EN.json index cb17439320..7c28dfd7b3 100644 --- a/apps/web/src/i18n/strings/en_EN.json +++ b/apps/web/src/i18n/strings/en_EN.json @@ -2484,26 +2484,6 @@ } }, "settings": { - "extended_profile_settings": { - "additional_information": "Additional Information", - "professional_title": "Title", - "specialization": "Specialization", - "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.", - "invalid_email": "Invalid email.", - "invalid_phone": "Invalid phone number." - }, "account": { "dialog_title": "Settings: Account", "title": "Account" @@ -3816,7 +3796,6 @@ }, "user_info": { "extended_profile": { - "header": "Additional infos", "title": "Title", "specialization": "Speciality", "practice_name": "Practice name", @@ -3825,7 +3804,6 @@ "business_tel": "Business phone", "website": "Website", "further_info": "Further infos / qualifications", - "not_provided": "Not provided", "error_loading_profile": "Error loading profile" }, "admin_tools_section": "Admin Tools", diff --git a/apps/web/src/i18n/strings/fr.json b/apps/web/src/i18n/strings/fr.json index 275b11858f..875c055384 100644 --- a/apps/web/src/i18n/strings/fr.json +++ b/apps/web/src/i18n/strings/fr.json @@ -2488,26 +2488,6 @@ } }, "settings": { - "extended_profile_settings": { - "additional_information": "Informations supplémentaires", - "professional_title": "Titre", - "specialization": "Spécialisation", - "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.", - "invalid_email": "Adresse e-mail invalide.", - "invalid_phone": "Numéro de téléphone invalide." - }, "account": { "dialog_title": "Paramètres : Compte", "title": "Compte" @@ -3820,7 +3800,6 @@ }, "user_info": { "extended_profile": { - "header": "Informations supplémentaires", "title": "Titre", "specialization": "Spécialisation", "practice_name": "Nom de l’entreprise", @@ -3829,7 +3808,6 @@ "business_tel": "Téléphone (professionnel)", "website": "Site web", "further_info": "Informations supplémentaires / qualifications", - "not_provided": "Non fourni", "error_loading_profile": "Erreur lors du chargement du profil" }, "admin_tools_section": "Outils d’administration", diff --git a/apps/web/src/i18n/strings/it.json b/apps/web/src/i18n/strings/it.json index 1789cc4d37..838d25dc41 100644 --- a/apps/web/src/i18n/strings/it.json +++ b/apps/web/src/i18n/strings/it.json @@ -2119,18 +2119,6 @@ } }, "settings": { - "extended_profile_settings": { - "additional_information": "Informazioni aggiuntive", - "professional_title": "Titolo", - "specialization": "Specializzazione", - "address": "Indirizzo", - "website": "Sito web", - "further_information": "Ulteriori informazioni / dettagli", - "saving_profile": "Salvataggio del profilo esteso...", - "failed_to_load": "Impossibile caricare le impostazioni del profilo esteso.", - "failed_to_save": "Impossibile salvare le impostazioni del profilo esteso.", - "server_not_supported": "Il tuo server non supporta le impostazioni del profilo esteso." - }, "all_rooms_home": "Mostra tutte le stanze nella pagina principale", "all_rooms_home_description": "Tutte le stanze in cui sei appariranno nella pagina principale.", "always_show_message_timestamps": "Mostra sempre l'orario dei messaggi", diff --git a/apps/web/src/i18n/strings/lt.json b/apps/web/src/i18n/strings/lt.json index 82cd8c6d66..69dae78298 100644 --- a/apps/web/src/i18n/strings/lt.json +++ b/apps/web/src/i18n/strings/lt.json @@ -2195,13 +2195,11 @@ }, "user_info": { "extended_profile": { - "header": "Informazioni aggiuntive", "title": "Titolo", "specialization": "Specializzazione", "address": "Indirizzo", "website": "Sito web", "further_info": "Ulteriori informazioni / qualifiche", - "not_provided": "Non fornito", "error_loading_profile": "Errore nel caricamento del profilo" }, "admin_tools_section": "Administratoriaus įrankiai", diff --git a/apps/web/src/stores/UserProfilesStore.ts b/apps/web/src/stores/UserProfilesStore.ts index e938aeefc9..2538f8390d 100644 --- a/apps/web/src/stores/UserProfilesStore.ts +++ b/apps/web/src/stores/UserProfilesStore.ts @@ -216,52 +216,6 @@ export class UserProfilesStore { return null; } - /** - * Set an extended profile property for the current user. - * - * @param property - The property key to set - * @param value - The value to set - * @returns true if successful, false otherwise - */ - public async setExtendedProfileProperty(property: string, value: ExtendedProfileValue): Promise { - try { - await this.client.setExtendedProfileProperty(property, value); - - const userId = this.client.getSafeUserId(); - if (!this.extendedProfiles.has(userId)) { - this.extendedProfiles.set(userId, new Map()); - } - this.extendedProfiles.get(userId)?.set(property, value); - - return true; - } catch (e) { - logger.warn(`Error setting extended profile property ${property}`, e); - return false; - } - } - - /** - * Delete an extended profile property for the current user. - * - * @param property - The property key to delete - * @returns true if successful, false otherwise - */ - public async deleteExtendedProfileProperty(property: string): Promise { - try { - await this.client.deleteExtendedProfileProperty(property); - - const userId = this.client.getSafeUserId(); - if (this.extendedProfiles.has(userId)) { - this.extendedProfiles.get(userId)?.delete(property); - } - - return true; - } catch (e) { - logger.warn(`Error deleting extended profile property ${property}`, e); - return false; - } - } - public flush(): void { this.profiles = new LruCache(cacheSize); this.knownProfiles = new LruCache(cacheSize); diff --git a/apps/web/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx b/apps/web/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx index 313f840313..0b38cc72f3 100644 --- a/apps/web/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx +++ b/apps/web/test/unit-tests/components/views/settings/UserProfileSettings-test.tsx @@ -18,7 +18,6 @@ import { mkStubRoom, stubClient } from "../../../../test-utils"; import { ToastContext, type ToastRack } from "../../../../../src/contexts/ToastContext"; import { OwnProfileStore } from "../../../../../src/stores/OwnProfileStore"; import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; -import { SdkContextClass } from "../../../../../src/contexts/SDKContext"; import Modal from "../../../../../src/Modal"; interface MockedAvatarSettingProps { @@ -87,9 +86,6 @@ describe("ProfileSettings", () => { beforeEach(() => { client = stubClient(); - // HealthChat's ExtendedProfileSettings reads the global SdkContextClass.instance.userProfilesStore, - // which requires the singleton client to be set (it is, on login, in the real app). - SdkContextClass.instance.client = client; toastRack = { displayToast: jest.fn().mockReturnValue(jest.fn()), }; diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx b/apps/web/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx index e3f9484e0b..b12ba3a4ab 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/AccountUserSettingsTab-test.tsx @@ -85,9 +85,6 @@ describe("", () => { stores = new SdkContextClass(); stores.client = mockClient; - // HealthChat's ExtendedProfileSettings (mounted via UserProfileSettings) reads the global - // SdkContextClass.instance.userProfilesStore, which requires the singleton client to be set. - SdkContextClass.instance.client = mockClient; // stub out this store completely to avoid mocking initialisation const mockOidcClientStore = {} as unknown as OidcClientStore; jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore); diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap index b75035becd..c6a31f0b12 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AccountUserSettingsTab-test.tsx.snap @@ -42,14 +42,14 @@ exports[` 3pids should display 3pid email addresses an > @@ -154,14 +154,14 @@ exports[` 3pids should display 3pid email addresses an