mirror of
https://github.com/Novaloop-AG/healthchat-web.git
synced 2026-11-03 16:10:05 +00:00
refactor(profile): drop extended-profile editing, keep right-panel display
Extended-profile fields (io.element.profile.*) are no longer edited in the client, so remove the edit form and the dead code that came with it, keeping only the read-only right-panel display (UserExtendedProfileInfo). - Remove ExtendedProfileSettings (edit form) + its Settings integration and CSS - Remove ExtendedProfileView (never-rendered, dead) + its CSS - Inline the EXT_PROFILE_KEY_* constants into UserExtendedProfileInfo so it no longer depends on the deleted settings component - Drop now-unused set/deleteExtendedProfileProperty from UserProfilesStore - Prune dead settings|extended_profile_settings|* i18n keys (and the unused user_info.extended_profile header/not_provided keys) across all locales - Regenerate _components.pcss - Revert UserProfileSettings/AccountUserSettingsTab tests (+snapshot) to master, since their adaptations existed only for the removed edit form
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<ExtendedProfileViewProps> = ({ userId }) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
const [profileData, setProfileData] = useState<ExtendedProfileData | null>(null);
|
||||
const [hasData, setHasData] = useState<boolean>(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 (
|
||||
<Container className="mx_ExtendedProfileView">
|
||||
{loading ? (
|
||||
<Flex justify="center" align="center" className="mx_ExtendedProfileView_loading">
|
||||
<InlineSpinner />
|
||||
</Flex>
|
||||
) : error ? (
|
||||
<Alert
|
||||
type="critical"
|
||||
title={_t("common|error")}
|
||||
className="mx_ExtendedProfileView_error"
|
||||
>
|
||||
{_t("settings|extended_profile_settings|failed_to_load")}
|
||||
</Alert>
|
||||
) : hasData ? (
|
||||
<div className="mx_ExtendedProfileView_content">
|
||||
{(profileData?.title || profileData?.specialization) && (
|
||||
<div className="mx_ExtendedProfileView_section mx_ExtendedProfileView_professional">
|
||||
{profileData.title && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<BadgeIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|professional_title")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.title}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData.specialization && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<StarIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|specialization")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.specialization}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.address && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<LocationIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|business_city")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">{profileData.address}</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.website && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<Flex align="center" gap="var(--cpd-space-2x)">
|
||||
<GlobeIcon className="mx_ExtendedProfileView_icon" />
|
||||
<div>
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|website")}</div>
|
||||
<div className="mx_ExtendedProfileView_value">
|
||||
<a
|
||||
href={profileData.website.startsWith("http") ? profileData.website : `https://${profileData.website}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className="mx_ExtendedProfileView_link"
|
||||
>
|
||||
{profileData.website}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileData?.furtherInfo && (
|
||||
<div className="mx_ExtendedProfileView_field">
|
||||
<div className="mx_ExtendedProfileView_further_info">
|
||||
<div className="mx_ExtendedProfileView_label">{_t("settings|extended_profile_settings|further_information")}</div>
|
||||
<div className="mx_ExtendedProfileView_value mx_ExtendedProfileView_multiline">{profileData.furtherInfo}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedProfileView;
|
||||
@@ -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<UserExtendedProfileInfoProps> = ({ userI
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchProfileData = async () => {
|
||||
const fetchProfileData = async (): Promise<void> => {
|
||||
try {
|
||||
setError(false);
|
||||
setLoading(true);
|
||||
|
||||
@@ -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 }) => (
|
||||
<>
|
||||
<InlineSpinner />
|
||||
{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<ExtendedProfileSettingsProps> = ({ className }) => {
|
||||
const [title, setTitle] = useState<string>("");
|
||||
const [specialization, setSpecialization] = 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 [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);
|
||||
const [saveError, setSaveError] = useState<boolean>(false);
|
||||
const [loadError, setLoadError] = useState<boolean>(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<HTMLInputElement>) => {
|
||||
setTitle(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onSpecializationChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSpecialization(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onWebsiteChanged = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setWebsite(e.target.value);
|
||||
}, []);
|
||||
|
||||
const onFurtherInfoChanged = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
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);
|
||||
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<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(
|
||||
<SpinnerToast>{_t("settings|extended_profile_settings|saving_profile")}</SpinnerToast>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className={classNames("mx_ExtendedProfileSettings", className)}>
|
||||
<div className="mx_ExtendedProfileSettings_section">
|
||||
<h3 className="mx_ExtendedProfileSettings_heading">{_t("settings|extended_profile_settings|additional_information")}</h3>
|
||||
|
||||
{loadError && (
|
||||
<Alert title={_t("common|error")} type="critical">
|
||||
{_t("settings|extended_profile_settings|failed_to_load")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_fields">
|
||||
<div className="mx_ExtendedProfileSettings_fieldsRow">
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_title">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|professional_title")}
|
||||
value={title}
|
||||
onChange={onTitleChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field mx_ExtendedProfileSettings_field_specialization">
|
||||
<Field
|
||||
label={_t("settings|extended_profile_settings|specialization")}
|
||||
value={specialization}
|
||||
onChange={onSpecializationChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
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")}
|
||||
value={website}
|
||||
onChange={onWebsiteChanged}
|
||||
disabled={isSaving}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mx_ExtendedProfileSettings_field">
|
||||
<Field
|
||||
className="mx_ExtendedProfileSettings_multiline"
|
||||
label={_t("settings|extended_profile_settings|further_information")}
|
||||
value={furtherInfo}
|
||||
onChange={onFurtherInfoChanged}
|
||||
disabled={isSaving}
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Single set of buttons for all fields */}
|
||||
<div className="mx_ExtendedProfileSettings_buttons">
|
||||
<AccessibleButton onClick={onCancel} kind="primary_outline" disabled={isSaving || !isModified}>
|
||||
{_t("common|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={onSave} kind="primary" disabled={isSaving || !isModified}>
|
||||
{_t("common|save")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
{saveError && (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtendedProfileSettings;
|
||||
@@ -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<UserProfileSettingsProps> = ({
|
||||
)}
|
||||
{userIdentifier && <UsernameBox username={userIdentifier} />}
|
||||
|
||||
{/* Always display the Extended Profile Settings component */}
|
||||
<ExtendedProfileSettings className="mx_UserProfileSettings_extendedProfile" />
|
||||
|
||||
<Flex gap="var(--cpd-space-4x)" className="mx_UserProfileSettings_profile_buttons">
|
||||
{externalAccountManagementUrl && (
|
||||
<ManageAccountButton externalAccountManagementUrl={externalAccountManagementUrl} />
|
||||
|
||||
@@ -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": "<strong>Einstellungen:</strong> 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",
|
||||
|
||||
@@ -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": "<strong>Settings:</strong> 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",
|
||||
|
||||
@@ -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": "<strong>Paramètres : </strong> 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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<boolean> {
|
||||
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<boolean> {
|
||||
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<string, IMatrixProfile | null>(cacheSize);
|
||||
this.knownProfiles = new LruCache<string, IMatrixProfile | null>(cacheSize);
|
||||
|
||||
@@ -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()),
|
||||
};
|
||||
|
||||
-3
@@ -85,9 +85,6 @@ describe("<AccountUserSettingsTab />", () => {
|
||||
|
||||
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);
|
||||
|
||||
+4
-4
@@ -42,14 +42,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||
>
|
||||
<input
|
||||
autocomplete="email"
|
||||
id="mx_Field_113"
|
||||
id="mx_Field_3"
|
||||
label="Email Address"
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_113"
|
||||
for="mx_Field_3"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
@@ -154,14 +154,14 @@ exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses an
|
||||
</span>
|
||||
<input
|
||||
autocomplete="tel-national"
|
||||
id="mx_Field_114"
|
||||
id="mx_Field_4"
|
||||
label="Phone Number"
|
||||
placeholder="Phone Number"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_Field_114"
|
||||
for="mx_Field_4"
|
||||
>
|
||||
Phone Number
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user