(null);
const { lessonId } = useParams();
const { success, error } = useMessage();
@@ -275,6 +278,10 @@ export function SideMenuEditorContent() {
const [reqUpdateLessonState] = useUpdateLessonStateMutation();
+ const lessonsComponentsCategories = t("lessonsComponents.categories", {
+ returnObjects: true,
+ }) as { [key: string]: string };
+
useEffect(() => {
// subscribe to the current page
const pathname = location.pathname;
@@ -319,7 +326,7 @@ export function SideMenuEditorContent() {
>
{componentsGroups.map((group, i) => (
- {group.category}
+ {lessonsComponentsCategories[group.category]}
+
@@ -423,6 +436,7 @@ export function DraggableCreateComponent({
}
function CreateComponent({ component }: { component: Component }) {
+ const { t } = useTranslation();
const { error } = useMessage();
const dispatch = useDispatch();
@@ -431,6 +445,14 @@ function CreateComponent({ component }: { component: Component }) {
const [reqAddLessonContent] = useAddLessonContentMutation();
+ const lessonsCommonComponents = t("lessonsComponents.commonComponents", {
+ returnObjects: true,
+ }) as { [key: string]: string };
+
+ const lessonsMediaComponents = t("lessonsComponents.mediaComponents", {
+ returnObjects: true,
+ }) as { [key: string]: string };
+
return (
{
try {
+ const defaultData =
+ component.type < 2
+ ? lessonsCommonComponents[component.name] ||
+ lessonsMediaComponents[component.name] ||
+ component.defaultData ||
+ ""
+ : component.defaultData || "";
+
const res = await reqAddLessonContent({
lessonId: currentLnId,
type: component.type,
- data: component.defaultData || "",
+ data: defaultData,
}).unwrap();
dispatch(
addLessonContent({
Id: res.Id,
Type: component.type,
- Data: component.defaultData || "",
+ Data: defaultData,
Page: 1,
Position: 1,
})
);
} catch (err) {
console.log("error", err);
- error("Failed to add content");
+ error(t("common.messageRequestFailed"));
}
}}
>
@@ -485,11 +515,13 @@ function CreateComponent({ component }: { component: Component }) {
) : null}
- {component.name}
+ {
+ {
+ ...lessonsCommonComponents,
+ ...lessonsMediaComponents,
+ }[component.name]
+ }
);
}
-
-//console.log("insert component", component.type);
-//dispatch(addLessonContent(component.type));
diff --git a/src/core/types/lesson.ts b/src/core/types/lesson.ts
index 4fdee43..daa3e4d 100644
--- a/src/core/types/lesson.ts
+++ b/src/core/types/lesson.ts
@@ -4,6 +4,7 @@ export interface Lesson {
Title: string;
ThumbnailUrl: string;
CreatorUserId: string;
+ QuestionsCount?: number;
CreatedAt: string;
}
@@ -17,6 +18,7 @@ export interface LessonSettings {
Title: string;
ThumbnailUrl: string;
State?: LessonState;
+ QuestionsCount?: number;
}
// used on lesson page and on the lesson editor
diff --git a/src/features/AiChat/index.tsx b/src/features/AiChat/index.tsx
index 21a6a76..a0e72e2 100644
--- a/src/features/AiChat/index.tsx
+++ b/src/features/AiChat/index.tsx
@@ -1,62 +1,112 @@
-import React from "react";
-import { FloatButton } from "antd";
-import { CommentOutlined } from "@ant-design/icons";
-import { DeepChat } from "deep-chat-react";
-import { getUserSessionFromLocalStorage } from "core/utils/utils";
+import React from 'react';
+import { FloatButton } from 'antd';
+import { CommentOutlined } from '@ant-design/icons';
+import { DeepChat } from 'deep-chat-react';
+import { getUserSessionFromLocalStorage } from 'core/utils/utils';
+
+import { Marked, marked } from 'marked';
+import { createDirectives, presetDirectiveConfigs } from 'marked-directive';
function AiChat() {
- const [visible, setVisible] = React.useState(false);
+ const [visible, setVisible] = React.useState(false);
- return (
- <>
- {visible ? (
-
- {
- console.log("onMessagee", message);
- }}
- >
-
- ) : null}
+ return (
+ <>
+ {visible ? (
+
+
{
+ let _response = { ...response };
- }
- type="primary"
- onClick={() => console.log("onClick")}
- style={{ zIndex: 10000 }}
- onClickCapture={() => {
- setVisible(!visible);
- }}
- />
- >
- );
+ let text = response.text;
+
+ delete _response.text;
+
+ // Regular expression to match pattern %URL%x%URL%
+ //const re = /%URL%(.*?)%URL%/g;
+ // Replace pattern with an HTML anchor tag
+ //text = text.replace(re, '@@[Mehr Infos]{href="$1"}');
+
+ _response.html = new Marked()
+ .use(
+ createDirectives([
+ ...presetDirectiveConfigs,
+ {
+ level: 'block',
+ marker: 'µ',
+ renderer(token) {
+ return `${token.text}`;
+ },
+ },
+ ])
+ )
+ .parse(text);
+
+ return _response;
+ }}
+ >
+
+ ) : null}
+
+ }
+ type="primary"
+ onClick={() => console.log('onClick')}
+ style={{ zIndex: 10000 }}
+ onClickCapture={() => {
+ setVisible(!visible);
+ }}
+ />
+ >
+ );
}
export default AiChat;
diff --git a/src/features/AiChat/style.module.css b/src/features/AiChat/style.module.css
new file mode 100644
index 0000000..efed1c1
--- /dev/null
+++ b/src/features/AiChat/style.module.css
@@ -0,0 +1,19 @@
+.aiButtonSource {
+ background-color: #35f;
+ color: #fff;
+ padding: 10px 20px;
+ margin: 0 0;
+ display: inline-block;
+ border-radius: 8px;
+ text-decoration: none;
+
+ cursor: pointer;
+
+ transition: all 0.3s ease 0s;
+}
+
+.aiButtonSource:hover {
+ background-color: #35f;
+ color: #fff;
+ border: 2px solid #35f;
+}
diff --git a/src/features/ContactSupport/index.tsx b/src/features/ContactSupport/index.tsx
index 09fd718..1db419f 100644
--- a/src/features/ContactSupport/index.tsx
+++ b/src/features/ContactSupport/index.tsx
@@ -1,16 +1,22 @@
import { Descriptions, Typography } from "antd";
import MyMiddleCard from "shared/components/MyMiddleCard";
import MyBanner from "shared/components/MyBanner";
+import HeaderBar from "core/components/Header";
+import { useTranslation } from "react-i18next";
export default function ContactSupport() {
+ const { t } = useTranslation();
+
return (
<>
-
+ }
+ />
-
+
- If you have any questions or need help, please contact us at the
- following e-mail address:
+ {t("contactSupport.paragraph")}
diff --git a/src/features/Lessons/LessonPage/index.tsx b/src/features/Lessons/LessonPage/index.tsx
index 6dea7f6..01b89a8 100644
--- a/src/features/Lessons/LessonPage/index.tsx
+++ b/src/features/Lessons/LessonPage/index.tsx
@@ -21,6 +21,7 @@ import {
setLessonPageCurrentLessonId,
} from "./lessonPageSlice";
import MyCenteredSpin from "shared/components/MyCenteredSpin";
+import { useTranslation } from "react-i18next";
const LessonContents: React.FC = () => {
const dispatch = useDispatch();
@@ -65,6 +66,8 @@ const LessonContents: React.FC = () => {
};
export default function LessonPage() {
+ const { t } = useTranslation();
+
const location = useLocation();
const navigate = useNavigate();
@@ -74,6 +77,7 @@ export default function LessonPage() {
theme="light"
backTo={Constants.ROUTE_PATHS.LESSIONS.ROOT}
onEdit={() => navigate(`${location.pathname}/editor`)}
+ sticky
/>
- }>
- Finish lesson
+ }
+ onClick={() => navigate(Constants.ROUTE_PATHS.LESSIONS.ROOT)}
+ >
+ {t("lessonPage.finishLessonButton")}
diff --git a/src/features/Lessons/LessonPageEditor/Droppable.tsx b/src/features/Lessons/LessonPageEditor/Droppable.tsx
index 86890cd..506b043 100644
--- a/src/features/Lessons/LessonPageEditor/Droppable.tsx
+++ b/src/features/Lessons/LessonPageEditor/Droppable.tsx
@@ -1,76 +1,90 @@
-import { closestCenter, closestCorners, DndContext, DragEndEvent, DragOverlay, MeasuringStrategy, rectIntersection, useDroppable } from '@dnd-kit/core';
-import { verticalListSortingStrategy, SortableContext, rectSwappingStrategy, rectSortingStrategy } from '@dnd-kit/sortable';
-import SortableEditorItem from './SortableEditorItem';
-import { store } from 'core/store/store';
-
-import { restrictToVerticalAxis, restrictToWindowEdges, snapCenterToCursor } from '@dnd-kit/modifiers';
-import { currentLessonId, onDragHandler } from './lessonPageEditorSlice';
-import { LessonContent } from 'core/types/lesson';
-import { useUpdateLessonContentPositionMutation } from 'core/services/lessons';
-import { useSelector } from 'react-redux';
-import React from 'react';
-import { Typography } from 'antd';
-import { HolderOutlined } from '@ant-design/icons';
+import {
+ closestCorners,
+ DndContext,
+ DragEndEvent,
+ DragOverlay,
+ useDroppable,
+} from "@dnd-kit/core";
+import {
+ verticalListSortingStrategy,
+ SortableContext,
+} from "@dnd-kit/sortable";
+import SortableEditorItem from "./SortableEditorItem";
+import { store } from "core/store/store";
+import { snapCenterToCursor } from "@dnd-kit/modifiers";
+import { currentLessonId, onDragHandler } from "./lessonPageEditorSlice";
+import { LessonContent } from "core/types/lesson";
+import { useUpdateLessonContentPositionMutation } from "core/services/lessons";
+import { useSelector } from "react-redux";
+import React from "react";
+import { HolderOutlined } from "@ant-design/icons";
const Droppable = ({ items }: { items: LessonContent[] }) => {
- const droppableID = 'editorComponentArea';
- const { setNodeRef } = useDroppable({ id: droppableID });
- const currentLnId = useSelector(currentLessonId);
+ const droppableID = "editorComponentArea";
+ const { setNodeRef } = useDroppable({ id: droppableID });
+ const currentLnId = useSelector(currentLessonId);
- const [reqUpdateLessonContentPosition] = useUpdateLessonContentPositionMutation();
+ const [reqUpdateLessonContentPosition] =
+ useUpdateLessonContentPositionMutation();
- const [isDragging, setIsDragging] = React.useState(false);
+ const [isDragging, setIsDragging] = React.useState(false);
- const itemIDs = items.map((item) => item.Id);
+ const itemIDs = items.map((item) => item.Id);
- const handleDragEnd = (event: DragEndEvent) => {
- console.log('drag end', event);
- setIsDragging(false);
+ const handleDragEnd = (event: DragEndEvent) => {
+ console.log("drag end", event);
+ setIsDragging(false);
- if (!event.over) return;
+ if (!event.over) return;
- const activeId = event.active.id;
- const overId = event.over.id;
+ const activeId = event.active.id;
+ const overId = event.over.id;
- if (activeId === overId) return;
+ if (activeId === overId) return;
- let oldIndex = itemIDs.findIndex((item) => item === activeId);
- let newIndex = itemIDs.findIndex((item) => item === overId);
+ let oldIndex = itemIDs.findIndex((item) => item === activeId);
+ let newIndex = itemIDs.findIndex((item) => item === overId);
- // store.dispatch(onDragHandler({ activeId, overId }));
+ // store.dispatch(onDragHandler({ activeId, overId }));
- store.dispatch(onDragHandler({ oldIndex, newIndex }));
+ store.dispatch(onDragHandler({ oldIndex, newIndex }));
- try {
- reqUpdateLessonContentPosition({
- lessonId: currentLnId,
- contentId: activeId,
- newPosition: newIndex + 1,
- });
- } catch (err) {
- console.error(err);
- }
- };
+ try {
+ reqUpdateLessonContentPosition({
+ lessonId: currentLnId,
+ contentId: activeId,
+ newPosition: newIndex + 1,
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ };
- return (
- {
- setIsDragging(true);
- }}
- onDragEnd={handleDragEnd}
- >
-
-
- {items.map((item) => (
-
- ))}
-
-
- {isDragging ? : null}
-
- );
+ return (
+ {
+ setIsDragging(true);
+ }}
+ onDragEnd={handleDragEnd}
+ >
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+
+ {isDragging ? : null}
+
+
+ );
};
/*
function handleDragEnd(event: DragEndEvent) {
diff --git a/src/features/Lessons/LessonPageEditor/SortableEditorItem.tsx b/src/features/Lessons/LessonPageEditor/SortableEditorItem.tsx
index b8e6c48..5597c53 100644
--- a/src/features/Lessons/LessonPageEditor/SortableEditorItem.tsx
+++ b/src/features/Lessons/LessonPageEditor/SortableEditorItem.tsx
@@ -19,9 +19,10 @@ import "./styles.module.css";
import { Converter } from "../converter";
import { useDeleteLessonContentMutation } from "core/services/lessons";
+/*
const animateLayoutChanges = (args: any) =>
args.isSorting || args.wasDragging ? defaultAnimateLayoutChanges(args) : true;
-
+*/
const SortableEditorItem = (props: { item: LessonContent }) => {
const lnContent = props.item;
const {
diff --git a/src/features/Lessons/LessonPageEditor/index.tsx b/src/features/Lessons/LessonPageEditor/index.tsx
index b12a587..48ec02a 100644
--- a/src/features/Lessons/LessonPageEditor/index.tsx
+++ b/src/features/Lessons/LessonPageEditor/index.tsx
@@ -52,6 +52,12 @@ const PreviewCard: React.FC = () => {
dispatch(setLessonThumbnailUrl(data.ThumbnailUrl));
}, [data]);
+ useEffect(() => {
+ addWebSocketReconnectListener(refetch);
+
+ return () => removeWebSocketReconnectListener(refetch);
+ }, []);
+
if (error) return ;
return (
@@ -150,6 +156,7 @@ export default function LessonPageEditor() {
)
)
}
+ sticky
/>
diff --git a/src/features/Lessons/Questions/index.tsx b/src/features/Lessons/Questions/index.tsx
index 54b5e11..b60702f 100644
--- a/src/features/Lessons/Questions/index.tsx
+++ b/src/features/Lessons/Questions/index.tsx
@@ -27,8 +27,11 @@ import {
} from "core/services/websocketService";
import { useCachedUser } from "core/services/cachedUser";
import MyUserAvatar from "shared/components/MyUserAvatar";
+import { useTranslation } from "react-i18next";
const CreateQuestionForm: React.FC = () => {
+ const { t } = useTranslation();
+
const { lessonId } = useParams();
const [form] = useForm();
@@ -42,11 +45,11 @@ const CreateQuestionForm: React.FC = () => {
message: form.getFieldValue("message"),
}).unwrap();
- success("Question created successfully");
+ success(t("lessonQuestions.messageQuestionSuccessfullyCreated"));
form.resetFields();
} catch (err) {
console.error(err);
- error("Failed to create question");
+ error(t("common.messageRequestFailed"));
}
};
@@ -58,12 +61,14 @@ const CreateQuestionForm: React.FC = () => {
requiredMark={false}
>
@@ -71,7 +76,7 @@ const CreateQuestionForm: React.FC = () => {
@@ -79,6 +84,8 @@ const CreateQuestionForm: React.FC = () => {
};
export default function Questions() {
+ const { t } = useTranslation();
+
const dispatch = useDispatch();
const { lessonId } = useParams();
@@ -108,7 +115,9 @@ export default function Questions() {
return (
- Questions
+
+ {t("lessonQuestions.questions")}
+
@@ -152,6 +161,7 @@ export function QuestionItem({
replies: LessonQuestion[];
likedQuestions: string[];
}) {
+ const { t } = useTranslation();
const [showReplies, setShowReplies] = useState(1);
const { success, error } = useMessage();
@@ -167,12 +177,12 @@ export function QuestionItem({
message: message,
}).unwrap();
- success("Question created successfully");
+ success(t("lessonQuestions.messageReplySuccessfullyCreated"));
await new Promise((resolve) => setTimeout(resolve, 0));
} catch (err) {
console.error(err);
- error("Failed to create question");
+ error(t("common.messageRequestFailed"));
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
@@ -185,7 +195,7 @@ export function QuestionItem({
}).unwrap();
} catch (err) {
console.error(err);
- error("Failed to like");
+ error(t("lessonQuestions.messageRequestFailed"));
}
}
@@ -197,7 +207,7 @@ export function QuestionItem({
}).unwrap();
} catch (err) {
console.error(err);
- error("Failed to dislike");
+ error(t("lessonQuestions.messageRequestFailed"));
}
}
@@ -258,6 +268,8 @@ export function QuestionUIRaw({
}) {
//const [hasLiked, setHasLiked] = useState(false);
+ const { t } = useTranslation();
+
const [replyFormVisible, setReplyFormVisible] = useState(false);
const [replyMessage, setReplyMessage] = useState(null);
const [isSendingReply, setIsSendingReply] = useState(false);
@@ -324,7 +336,9 @@ export function QuestionUIRaw({
}, 100);
}}
>
- {replyFormVisible ? "Hide" : "Reply"}
+ {replyFormVisible
+ ? t("lessonQuestions.hide")
+ : t("lessonQuestions.reply")}
{replyFormVisible ? (
@@ -344,12 +358,17 @@ export function QuestionUIRaw({
>
setReplyMessage(e.target.value)}
autoSize={{ minRows: 2 }}
maxLength={5000}
@@ -361,7 +380,7 @@ export function QuestionUIRaw({
loading={isSendingReply}
htmlType="submit"
>
- Reply
+ {t("lessonQuestions.reply")}
diff --git a/src/features/Lessons/converter.tsx b/src/features/Lessons/converter.tsx
index ebe5b1f..3ac82a6 100644
--- a/src/features/Lessons/converter.tsx
+++ b/src/features/Lessons/converter.tsx
@@ -1,380 +1,410 @@
-import { LessonContent } from 'core/types/lesson';
-import { getTypeByName } from './components';
-import { Button, Input, Typography, Flex } from 'antd';
-import { useUpdateLessonContentMutation } from 'core/services/lessons';
-import { useSelector } from 'react-redux';
-import { currentLessonId } from './LessonPageEditor/lessonPageEditorSlice';
-import { useRef } from 'react';
-import MyUpload from 'shared/components/MyUpload';
-import { Constants } from 'core/utils/utils';
-import { MediaPlayer, MediaProvider } from '@vidstack/react';
-import { defaultLayoutIcons, DefaultVideoLayout } from '@vidstack/react/player/layouts/default';
-import '@vidstack/react/player/styles/default/theme.css';
-import '@vidstack/react/player/styles/default/layouts/video.css';
-import { darkMode } from 'core/reducers/appSlice';
+import { LessonContent } from "core/types/lesson";
+import { getTypeByName } from "./components";
+import { Button, Input, Typography, Flex } from "antd";
+import { useUpdateLessonContentMutation } from "core/services/lessons";
+import { useSelector } from "react-redux";
+import { currentLessonId } from "./LessonPageEditor/lessonPageEditorSlice";
+import { useRef } from "react";
+import MyUpload from "shared/components/MyUpload";
+import { Constants } from "core/utils/utils";
+import { MediaPlayer, MediaProvider } from "@vidstack/react";
+import {
+ defaultLayoutIcons,
+ DefaultVideoLayout,
+} from "@vidstack/react/player/layouts/default";
+import "@vidstack/react/player/styles/default/theme.css";
+import "@vidstack/react/player/styles/default/layouts/video.css";
+import { darkMode } from "core/reducers/appSlice";
+import { useTranslation } from "react-i18next";
const extractVideoId = (url: string) => {
- // regex to extract video id from youtube url
- const regex = /(?:https?:\/\/)?(?:www\.)?youtube\.com\/(?:watch\?v=|embed\/|v\/|.+\?v=|.+\/|.+v=)?([^"&?\/\s]{11})/;
- const match = url.match(regex);
- return match ? match[1] : url;
+ // regex to extract video id from youtube url
+ const regex =
+ /(?:https?:\/\/)?(?:www\.)?youtube\.com\/(?:watch\?v=|embed\/|v\/|.+\?v=|.+\/|.+v=)?([^"&?\/\s]{11})/;
+ const match = url.match(regex);
+ return match ? match[1] : url;
};
-export function Converter({ mode, lessonContent, onEdit }: { mode: 'view' | 'edititable'; lessonContent: LessonContent; onEdit?: (newData: string) => void }) {
- const lessonId = useSelector(currentLessonId);
+export function Converter({
+ mode,
+ lessonContent,
+ onEdit,
+}: {
+ mode: "view" | "edititable";
+ lessonContent: LessonContent;
+ onEdit?: (newData: string) => void;
+}) {
+ const { t } = useTranslation();
+ const debounceRef = useRef(null);
- const isDarkMode = useSelector(darkMode);
+ const lessonId = useSelector(currentLessonId);
+ const isDarkMode = useSelector(darkMode);
- const [reqUpdateLessonContent] = useUpdateLessonContentMutation();
+ const [reqUpdateLessonContent] = useUpdateLessonContentMutation();
- const debounceRef = useRef(null);
+ switch (lessonContent.Type) {
+ case getTypeByName("Header"):
+ if (mode === "view") {
+ return (
+
+ {lessonContent.Data}
+
+ );
+ }
- switch (lessonContent.Type) {
- case getTypeByName('Header'):
- if (mode === 'view') {
- return {lessonContent.Data}
;
+ return (
+ {
+ onEdit?.(event);
+
+ try {
+ reqUpdateLessonContent({
+ lessonId: lessonId,
+ contentId: lessonContent.Id,
+ data: event,
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ },
+ }}
+ level={1}
+ style={{
+ margin: 0,
+ width: "100%",
+ }}
+ >
+ {lessonContent.Data}
+
+ );
+ case getTypeByName("Text"):
+ if (mode === "view") {
+ const formattedText = lessonContent.Data.split("\n").map(
+ (line, index) => {line || "\u00A0"}
+ );
+
+ return (
+
+ );
+ }
+
+ return (
+ {
+ onEdit?.(event.target.value);
+
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
}
- return (
- {
- onEdit?.(event);
+ debounceRef.current = setTimeout(() => {
+ try {
+ reqUpdateLessonContent({
+ lessonId: lessonId,
+ contentId: lessonContent.Id,
+ data: event.target.value,
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ }, 1000);
+ }}
+ />
+ );
+ case getTypeByName("Image"):
+ if (mode === "view" && lessonContent.Data === "") {
+ return (
+
+
+ {t("lessonComponentsConverter.noImageProvided")}
+
+
+ );
+ }
- try {
- reqUpdateLessonContent({
- lessonId: lessonId,
- contentId: lessonContent.Id,
- data: event,
- });
- } catch (err) {
- console.error(err);
- }
- },
- }}
- level={1}
- style={{
- margin: 0,
- width: '100%',
- }}
- >
- {lessonContent.Data}
-
- );
- case getTypeByName('Text'):
- if (mode === 'view') {
- const formattedText = lessonContent.Data.split('\n').map((line, index) => {line || '\u00A0'});
+ const GalleryUpload = () => {
+ return (
+ {
+ if (info.file.status === "done") {
+ onEdit?.(info.file.response.Data);
+ }
+ }}
+ imgCropProps={{
+ aspect: 5 / 4,
+ children: <>>,
+ }}
+ >
+
+
+ );
+ };
- return (
-
- );
- }
+ if (lessonContent.Data === "") {
+ return (
+
+
+ {t("lessonComponentsConverter.chooseImageFrom")}
- return (
- {
- console.log('edit');
+
+
+
+ );
+ }
- onEdit?.(event.target.value);
+ return (
+
+
- if (debounceRef.current) {
- clearTimeout(debounceRef.current);
- }
+ {mode === "edititable" && (
+
+
+
+ {t("lessonComponentsConverter.chooseAnotherImageFrom")}
+
+
+
+
+ )}
+
+ );
+ case getTypeByName("YouTube"):
+ const videoId = extractVideoId(lessonContent.Data);
- debounceRef.current = setTimeout(() => {
- try {
- reqUpdateLessonContent({
- lessonId: lessonId,
- contentId: lessonContent.Id,
- data: event.target.value,
- });
- } catch (err) {
- console.error(err);
- }
- }, 1000);
- }}
- />
- );
- case getTypeByName('Image'):
- console.log('image', lessonContent.Data);
+ return (
+
+
- if (mode === 'view' && lessonContent.Data === '') {
- return (
-
-
- No image provided
-
-
- );
- }
+ {mode === "edititable" && (
+ <>
+
+ {t("lessonComponentsConverter.videoId")}
+
+ {
+ onEdit?.(event.target.value);
- const GalleryUpload = () => {
- return (
- {
- if (info.file.status === 'done') {
- console.log('done');
- onEdit?.(info.file.response.Data);
- }
- }}
- imgCropProps={{
- aspect: 5 / 4,
- children: <>>,
- }}
- >
-
-
- );
- };
+ if (debounceRef.current) clearTimeout(debounceRef.current);
- if (lessonContent.Data === '') {
- return (
-
-
- Choose image from
+ if (event.target.value === "") return;
-
-
-
- );
- }
+ debounceRef.current = setTimeout(() => {
+ try {
+ reqUpdateLessonContent({
+ lessonId: lessonId,
+ contentId: lessonContent.Id,
+ data: extractVideoId(event.target.value),
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ }, 1000);
+ }}
+ />
+ >
+ )}
+
+ );
+ case getTypeByName("Video"):
+ if (mode === "view" && lessonContent.Data === "") {
+ return (
+
+
+ {t("lessonComponentsConverter.noVideoProvided")}
+
+
+ );
+ }
- return (
-
-
+ const VideoUpload = () => {
+ return (
+ {
+ if (info.file.status === "done") {
+ onEdit?.(info.file.response.Data);
+ }
+ }}
+ accept={Constants.ACCEPTED_VIDEO_FILE_TYPES}
+ >
+
+
+ );
+ };
- {mode === 'edititable' && (
-
-
- Choose another image from
-
-
-
- )}
-
- );
- case getTypeByName('YouTube'):
- const videoId = extractVideoId(lessonContent.Data);
+ if (lessonContent.Data === "") {
+ return (
+
+
+ {t("lessonComponentsConverter.chooseVideoFrom")}
- console.log('videoId', videoId);
+
+
+
+ );
+ }
- return (
-
-
+ return (
+
+
+
+
+
- {mode === 'edititable' && (
- <>
- Video ID
- {
- console.warn('edit', event.target.value, videoId);
+ {mode === "edititable" && (
+
+
+
+ {t("lessonComponentsConverter.chooseAnotherVideoFrom")}
+
+
+
+
+ )}
+
+ );
- onEdit?.(event.target.value);
+ case getTypeByName("Iframe"):
+ return (
+
+ {t("lessonComponentsConverter.notImplemented")}
+
+ );
+ case getTypeByName("Banner"):
+ return (
+
+ {t("lessonComponentsConverter.notImplemented")}
+
+ );
- if (debounceRef.current) {
- clearTimeout(debounceRef.current);
- }
-
- if (event.target.value === '') {
- return;
- }
-
- debounceRef.current = setTimeout(() => {
- try {
- reqUpdateLessonContent({
- lessonId: lessonId,
- contentId: lessonContent.Id,
- data: extractVideoId(event.target.value),
- });
- } catch (err) {
- console.error(err);
- }
- }, 1000);
- }}
- />
- >
- )}
-
- );
- case getTypeByName('Video'):
- if (mode === 'view' && lessonContent.Data === '') {
- return (
-
-
- No video provided
-
-
- );
- }
-
- const VideoUpload = () => {
- return (
- {
- if (info.file.status === 'done') {
- console.log('done');
- onEdit?.(info.file.response.Data);
- }
- }}
- accept={Constants.ACCEPTED_VIDEO_FILE_TYPES}
- >
-
-
- );
- };
-
- if (lessonContent.Data === '') {
- return (
-
-
- Choose video from
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
- {mode === 'edititable' && (
-
-
- Choose another video from
-
-
-
- )}
-
- );
-
- case getTypeByName('Iframe'):
- return Not implemented
;
- case getTypeByName('Banner'):
- return Not implemented
;
-
- default:
- return Unknown type
;
- }
+ default:
+ return {t("lessonComponentsConverter.unkownType")}
;
+ }
}
diff --git a/src/features/Lessons/index.tsx b/src/features/Lessons/index.tsx
index f82fefd..f8a670e 100644
--- a/src/features/Lessons/index.tsx
+++ b/src/features/Lessons/index.tsx
@@ -16,15 +16,23 @@ import LessonPreviewCard from "shared/components/MyLessonPreviewCard";
import { useMessage } from "core/context/MessageContext";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { lessons, setLessons } from "./lessonsSlice";
+import {
+ lessons,
+ searchFilter,
+ setLessons,
+ setSearchFilter,
+} from "./lessonsSlice";
import {
addWebSocketReconnectListener,
removeWebSocketReconnectListener,
} from "core/services/websocketService";
import MyCenteredSpin from "shared/components/MyCenteredSpin";
+import { useTranslation } from "react-i18next";
const CreateLessonButton: React.FC = () => {
const navigate = useNavigate();
+ const { t } = useTranslation();
+
const [createLesson, { isLoading }] = useCreateLessonMutation();
const { success, error } = useMessage();
@@ -33,7 +41,7 @@ const CreateLessonButton: React.FC = () => {
const res = await createLesson({}).unwrap();
if (res && res.Id) {
- success("Lesson created successfully ");
+ success(t("lessons.messageLessonSuccessfullyCreated"));
navigate(
Constants.ROUTE_PATHS.LESSIONS.PAGE_EDITOR.replace(
@@ -44,7 +52,7 @@ const CreateLessonButton: React.FC = () => {
}
} catch (err) {
console.error(err);
- error("Failed to create lesson");
+ error(t("common.messageRequestFailed"));
}
};
@@ -54,14 +62,16 @@ const CreateLessonButton: React.FC = () => {
onClick={handleCreateLesson}
loading={isLoading}
>
- Create
+ {t("common.create")}
);
};
const LessonList: React.FC = () => {
+ const { t } = useTranslation();
const dispatch = useDispatch();
const dataLessons = useSelector(lessons);
+ const dataSearchFilter = useSelector(searchFilter);
const { data, error, isLoading, refetch } = useGetLessonsQuery(undefined, {
refetchOnMountOrArgChange: true,
@@ -84,8 +94,12 @@ const LessonList: React.FC = () => {
if (!dataLessons || dataLessons.length === 0) return ;
- const publishedItems = dataLessons.filter((item) => item.State === 1);
- const unpublishedItems = dataLessons.filter((item) => item.State === 2);
+ const filteredItems = dataLessons.filter((item) =>
+ item.Title.toLowerCase().includes(dataSearchFilter.toLowerCase())
+ );
+
+ const publishedItems = filteredItems.filter((item) => item.State === 1);
+ const unpublishedItems = filteredItems.filter((item) => item.State === 2);
return (
<>
@@ -100,6 +114,7 @@ const LessonList: React.FC = () => {
lessonSettings={{
Title: item.Title,
ThumbnailUrl: item.ThumbnailUrl,
+ QuestionsCount: item.QuestionsCount,
}}
/>
))}
@@ -109,7 +124,7 @@ const LessonList: React.FC = () => {
{unpublishedItems.length > 0 && (
<>
- Unpublished
+ {t("lessons.lessonStatusDrafts")}
{unpublishedItems.map((item, index) => (
@@ -121,6 +136,7 @@ const LessonList: React.FC = () => {
lessonSettings={{
Title: item.Title,
ThumbnailUrl: item.ThumbnailUrl,
+ QuestionsCount: item.QuestionsCount,
}}
/>
))}
@@ -131,20 +147,29 @@ const LessonList: React.FC = () => {
};
export default function Lessons() {
- const onSearch: SearchProps["onSearch"] = (value, _e, info) =>
- console.log(info?.source, value);
+ const { t } = useTranslation();
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ return () => {
+ dispatch(setSearchFilter(""));
+ };
+ }, []);
return (
<>
- } />
+ } />
{
+ dispatch(setSearchFilter(e.target.value));
+ }}
style={{ width: 300 }}
allowClear
/>
diff --git a/src/features/Lessons/lessonsSlice.ts b/src/features/Lessons/lessonsSlice.ts
index a25438b..bfcb141 100644
--- a/src/features/Lessons/lessonsSlice.ts
+++ b/src/features/Lessons/lessonsSlice.ts
@@ -10,6 +10,7 @@ export const lessonsSlice = createSlice({
name: "lessons",
initialState: {
lessons: [] as Lesson[],
+ searchFilter: "",
},
reducers: {
setLessons: (state, action) => {
@@ -45,9 +46,13 @@ export const lessonsSlice = createSlice({
lesson.State = action.payload.State;
}
},
+ setSearchFilter: (state, action) => {
+ state.searchFilter = action.payload;
+ },
},
selectors: {
lessons: (state) => state.lessons,
+ searchFilter: (state) => state.searchFilter,
},
});
@@ -57,6 +62,7 @@ export const {
updateLessonPreviewTitle,
updateLessonPreviewThumbnail,
updateLessonState,
+ setSearchFilter,
} = lessonsSlice.actions;
-export const { lessons } = lessonsSlice.selectors;
+export const { lessons, searchFilter } = lessonsSlice.selectors;
diff --git a/src/features/Roles/index.tsx b/src/features/Roles/index.tsx
index c04aae1..1c632e4 100644
--- a/src/features/Roles/index.tsx
+++ b/src/features/Roles/index.tsx
@@ -12,8 +12,12 @@ import {
addWebSocketReconnectListener,
removeWebSocketReconnectListener,
} from "core/services/websocketService";
+import MyUserAvatar from "shared/components/MyUserAvatar";
+import { useTranslation } from "react-i18next";
export default function Roles() {
+ const { t } = useTranslation();
+
const { data, error, isLoading, refetch } = useGetRolesQuery(undefined, {
refetchOnMountOrArgChange: true,
});
@@ -26,7 +30,11 @@ export default function Roles() {
return (
<>
- } />
+ }
+ />
permission.category === "Team"
);
- const rolePermissions = tmpI18nObj.filter(
+ const rolePermissions = tmpI18nRoles.filter(
(permission) => permission.category === "Roles"
);
@@ -130,7 +150,7 @@ function RoleComponent({ role }: { role: Role }) {
items={[
{
key: "1",
- label: "Team",
+ label: t("roles.collapseTitleTeam"),
children: (
<>
{teamPermissions.map((permission, index) => (
@@ -141,7 +161,7 @@ function RoleComponent({ role }: { role: Role }) {
},
{
key: "2",
- label: "Roles",
+ label: t("roles.collapseTitleRoles"),
children: (
<>
{rolePermissions.map((permission, index) => (
@@ -168,9 +188,13 @@ function RoleComponent({ role }: { role: Role }) {
title={`${user.FirstName} ${user.LastName}`}
placement="top"
>
-
- {user.FirstName[0]}
-
+
+
+
))}
diff --git a/src/features/Settings/index.tsx b/src/features/Settings/index.tsx
index 4a214ad..5998453 100644
--- a/src/features/Settings/index.tsx
+++ b/src/features/Settings/index.tsx
@@ -30,6 +30,7 @@ import {
addWebSocketReconnectListener,
removeWebSocketReconnectListener,
} from "core/services/websocketService";
+import { useTranslation } from "react-i18next";
type GeneralFieldType = {
primaryColor: string | AggregationColor;
@@ -37,6 +38,8 @@ type GeneralFieldType = {
};
export default function Settings() {
+ const { t } = useTranslation();
+
const { data, error, isLoading, refetch } = useGetOrganizationSettingsQuery(
undefined,
{
@@ -54,7 +57,11 @@ export default function Settings() {
return (
<>
- } />
+ }
+ />
@@ -69,6 +76,7 @@ function GeneralCard({
data,
isLoading,
}: { data?: OrganizationSettings; isLoading?: boolean } = {}) {
+ const { t } = useTranslation();
const [form] = useForm();
const { success, error: errorMessage } = useMessage();
@@ -93,10 +101,12 @@ function GeneralCard({
currentPrimaryColor.current = hexColor;
- success("Settings updated successfully!");
+ success(
+ t("organizationSettings.generalCard.messageSettingsSuccessfullyUpdated")
+ );
} catch (error) {
console.error(error);
- errorMessage("Failed to update settings!");
+ errorMessage(t("common.messageRequestFailed"));
}
};
@@ -129,7 +139,7 @@ function GeneralCard({
padding: 16,
},
}}
- title="General"
+ title={t("organizationSettings.generalCard.title")}
loading={isLoading}
extra={
@@ -174,6 +187,7 @@ function GeneralCard({
function MediaCard({
isLoading,
}: { data?: OrganizationSettings; isLoading?: boolean } = {}) {
+ const { t } = useTranslation();
const { success } = useMessage();
const dispatch = useDispatch();
@@ -182,15 +196,22 @@ function MediaCard({
return (
diff --git a/src/features/Team/index.tsx b/src/features/Team/index.tsx
index de5180b..623b75a 100644
--- a/src/features/Team/index.tsx
+++ b/src/features/Team/index.tsx
@@ -15,15 +15,16 @@ import MyErrorResult from "shared/components/MyResult";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setTeamMembers, teamMembers } from "./teamSlice";
-import { tmpRoleNames } from "features/Roles";
import {
addWebSocketReconnectListener,
removeWebSocketReconnectListener,
} from "core/services/websocketService";
import { useMessage } from "core/context/MessageContext";
import MyUserAvatar from "shared/components/MyUserAvatar";
+import { useTranslation } from "react-i18next";
const TeamList: React.FC = () => {
+ const { t } = useTranslation();
const dispatch = useDispatch();
const { success, error: errorMessage } = useMessage();
@@ -41,41 +42,45 @@ const TeamList: React.FC = () => {
const [reqDeleteTeamMember, { isLoading: loadingDeleteTeamMember }] =
useDeleteTeamMemberMutation();
+ const tmpRoleNames = t("roles.roleNames", {
+ returnObjects: true,
+ }) as any;
+
const getTableContent = () => {
let items = [
{
- title: "First name",
+ title: t("common.firstName"),
dataIndex: "firstName",
key: "firstName",
},
{
- title: "Last name",
+ title: t("common.lastName"),
dataIndex: "lastName",
key: "lastName",
},
{
- title: "Email",
+ title: t("common.email"),
dataIndex: "email",
key: "email",
},
{
- title: "Role",
+ title: t("common.role"),
dataIndex: "role",
key: "role",
},
{
- title: "Status",
+ title: t("common.status"),
dataIndex: "status",
key: "status",
},
{
- title: "Actions",
+ title: t("common.actions"),
dataIndex: "actions",
key: "actions",
render: (_: any, record: any) => (
{
try {
await reqUpdateTeamMemberRole({
@@ -83,13 +88,18 @@ const TeamList: React.FC = () => {
roleId: selectedRoleId,
}).unwrap();
- success("Role updated successfully");
+ success(
+ t(
+ "team.popConfirmRoleChange.messageUpdatedRoleSuccessfully"
+ )
+ );
} catch (error) {
console.error(error);
- errorMessage("Error updating role");
+ errorMessage(t("common.messageRequestFailed"));
}
}}
okButtonProps={{ loading: loadingUpdateTeamMemberRole }}
+ cancelText={t("common.cancel")}
description={
{
try {
await reqDeleteTeamMember(record.key).unwrap();
- success("Team member deleted successfully");
+ success(
+ t(
+ "team.popConfirmDeleteMember.messageDeletedMemberSuccessfully"
+ )
+ );
} catch (error) {
console.error(error);
- errorMessage("Error deleting team member");
+ errorMessage(t("common.messageRequestFailed"));
}
}}
>
-
+
),
@@ -199,9 +214,15 @@ const TeamList: React.FC = () => {
};
export default function Team() {
+ const { t } = useTranslation();
+
return (
<>
- } />
+ }
+ />
- }>Invite new member
+ }>
+ {t("team.addMemberButton")}
+
diff --git a/src/features/UserProfile/index.tsx b/src/features/UserProfile/index.tsx
index b6a24ff..bfb3933 100644
--- a/src/features/UserProfile/index.tsx
+++ b/src/features/UserProfile/index.tsx
@@ -1,15 +1,14 @@
import {
- Avatar,
Card,
Descriptions,
Flex,
Form,
Input,
+ Select,
Typography,
} from "antd";
import HeaderBar from "../../core/components/Header";
import MyBanner from "../../shared/components/MyBanner";
-import { Constants } from "core/utils/utils";
import MyMiddleCard from "shared/components/MyMiddleCard";
import Meta from "antd/es/card/Meta";
import { useGetUserProfileQuery } from "core/services/userProfile";
@@ -31,15 +30,16 @@ import {
setProfilePictureUrl,
setRoleId,
} from "./userProfileSlice";
-import { tmpRoleNames } from "features/Roles";
import MyErrorResult from "shared/components/MyResult";
import MyUpload from "shared/components/MyUpload";
import { useMessage } from "core/context/MessageContext";
import MyUserAvatar from "shared/components/MyUserAvatar";
import { setUserProfilePictureUrl } from "core/reducers/appSlice";
+import { useTranslation } from "react-i18next";
export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
const dispatch = useDispatch();
+ const { t, i18n } = useTranslation();
const { success } = useMessage();
const dataProfilePictureUrl = useSelector(profilePictureUrl);
@@ -55,6 +55,10 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
}
);
+ const tmpRoleNames = t("roles.roleNames", {
+ returnObjects: true,
+ }) as any;
+
function AdminWrapper({ children }: { children: React.ReactNode }) {
if (!isAdmin) {
return <>{children}>;
@@ -98,12 +102,15 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
return (
<>
}
/>
-
+
{error ? (
) : (
@@ -115,31 +122,60 @@ export default function UserProfile({ isAdmin }: { isAdmin?: boolean }) {
},
}}
>
- {
- if (info.file.status === "done") {
- success("Profile picture updated successfully");
+
),
},
{
key: "2",
- label: "Last name",
+ label: t("common.lastName"),
children: (
),
},
{
key: "3",
- label: "Email",
+ label: t("common.email"),
children: ,
},
]}
diff --git a/src/features/WhatsNew/index.tsx b/src/features/WhatsNew/index.tsx
index be07f20..3b837b4 100644
--- a/src/features/WhatsNew/index.tsx
+++ b/src/features/WhatsNew/index.tsx
@@ -1,7 +1,13 @@
+import HeaderBar from "core/components/Header";
+import { useTranslation } from "react-i18next";
+import MyBanner from "shared/components/MyBanner";
+
export default function WhatsNew() {
- return (
- <>
- WhatsNew
- >
- );
+ const { t } = useTranslation();
+
+ return (
+ <>
+ } />
+ >
+ );
}
diff --git a/src/i18n.ts b/src/i18n.ts
new file mode 100644
index 0000000..2ff1b74
--- /dev/null
+++ b/src/i18n.ts
@@ -0,0 +1,18 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+import Backend from "i18next-http-backend";
+import LanguageDetector from "i18next-browser-languagedetector";
+
+i18n
+ .use(initReactI18next) // passes i18n down to react-i18next
+ .use(LanguageDetector)
+ .use(Backend)
+ .init({
+ supportedLngs: ["en", "de"],
+ fallbackLng: "de",
+ interpolation: {
+ escapeValue: false, // react already safes from xss
+ },
+ });
+
+export default i18n;
diff --git a/src/index.tsx b/src/index.tsx
index e0aa384..8f25a18 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -6,6 +6,7 @@ import { Provider } from "react-redux";
import { store } from "./core/store/store";
import { Suspense } from "react";
import MyCenteredSpin from "./shared/components/MyCenteredSpin";
+import "./i18n";
// import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
diff --git a/src/shared/components/MyEmpty/index.tsx b/src/shared/components/MyEmpty/index.tsx
index 567730b..4db8933 100644
--- a/src/shared/components/MyEmpty/index.tsx
+++ b/src/shared/components/MyEmpty/index.tsx
@@ -1,5 +1,18 @@
import { Empty } from "antd";
+import { useTranslation } from "react-i18next";
-export default function MyEmpty() {
- return
-}
\ No newline at end of file
+interface MyEmptyProps extends React.ComponentProps {
+ children?: React.ReactNode;
+}
+
+const MyEmpty: React.FC = ({ children, ...props }) => {
+ const { t } = useTranslation();
+
+ return (
+
+ {children}
+
+ );
+};
+
+export default MyEmpty;
diff --git a/src/shared/components/MyLessonPreviewCard/index.tsx b/src/shared/components/MyLessonPreviewCard/index.tsx
index 4adc689..8687259 100644
--- a/src/shared/components/MyLessonPreviewCard/index.tsx
+++ b/src/shared/components/MyLessonPreviewCard/index.tsx
@@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import MyUpload from "shared/components/MyUpload";
import styles from "./styles.module.css";
import { LessonSettings } from "core/types/lesson";
+import { useTranslation } from "react-i18next";
export default function MyLessonPreviewCard({
mode = "view",
@@ -21,6 +22,8 @@ export default function MyLessonPreviewCard({
onEditTitle?: (newTitle: string) => void;
onThumbnailChanged?: () => void;
}) {
+ const { t } = useTranslation();
+
const LinkWrapper = ({ children }: { children: React.ReactNode }) => {
if (mode === "editable") return <>{children}>;
@@ -74,7 +77,8 @@ export default function MyLessonPreviewCard({
{mode === "view" ? (
{lessonSettings.Title}
-
12 comments
+
{lessonSettings.QuestionsCount}{" "}
+ {t("lessonPage.questions")}
) : (
);
}
diff --git a/src/shared/components/MyUserAvatar/index.tsx b/src/shared/components/MyUserAvatar/index.tsx
index 070971b..413b0bc 100644
--- a/src/shared/components/MyUserAvatar/index.tsx
+++ b/src/shared/components/MyUserAvatar/index.tsx
@@ -1,34 +1,55 @@
-import { Avatar } from 'antd';
-import { Constants } from 'core/utils/utils';
-import { UserOutlined } from '@ant-design/icons';
-import { primaryColor } from 'core/reducers/appSlice';
-import { useSelector } from 'react-redux';
+import { Avatar } from "antd";
+import { Constants } from "core/utils/utils";
+import { UserOutlined } from "@ant-design/icons";
+import { primaryColor } from "core/reducers/appSlice";
+import { useSelector } from "react-redux";
interface MyUserAvatarProps {
- size?: number;
- firstName?: string;
- profilePictureUrl: string;
- disableCursorPointer?: boolean;
+ size?: number;
+ firstName?: string;
+ profilePictureUrl: string;
+ disableCursorPointer?: boolean;
}
-const MyUserAvatar: React.FC = ({ size = 56, firstName, profilePictureUrl, disableCursorPointer }) => {
- const appPrimaryColor = useSelector(primaryColor);
+const MyUserAvatar: React.FC = ({
+ size = 56,
+ firstName,
+ profilePictureUrl,
+ disableCursorPointer,
+}) => {
+ const appPrimaryColor = useSelector(primaryColor);
- const defaultStyle = disableCursorPointer === undefined ? { cursor: 'pointer' } : {};
+ const defaultStyle =
+ disableCursorPointer === undefined ? { cursor: "pointer" } : {};
- const isProfilePictureEmpty = profilePictureUrl === '';
- const avatarContent = isProfilePictureEmpty && firstName !== undefined ? firstName.charAt(0) : undefined;
- const iconContent = isProfilePictureEmpty && firstName === undefined ? : undefined;
- const avatarSrc = isProfilePictureEmpty ? undefined : `${Constants.STATIC_CONTENT_ADDRESS}/${profilePictureUrl}`;
- const avatarStyle = isProfilePictureEmpty ? { ...defaultStyle, backgroundColor: `#${appPrimaryColor}` } : defaultStyle;
+ const isProfilePictureEmpty = profilePictureUrl === "";
+ const avatarContent =
+ isProfilePictureEmpty && firstName !== undefined
+ ? firstName.charAt(0)
+ : undefined;
+ const iconContent =
+ isProfilePictureEmpty && firstName === undefined ? (
+
+ ) : undefined;
+ const avatarSrc = isProfilePictureEmpty
+ ? undefined
+ : `${Constants.STATIC_CONTENT_ADDRESS}/${profilePictureUrl}`;
+ const avatarStyle = isProfilePictureEmpty
+ ? { ...defaultStyle, backgroundColor: `#${appPrimaryColor}` }
+ : defaultStyle;
- return (
-
- );
+ return (
+
+ );
};
export default MyUserAvatar;