From c236b85cf74544bc266e1d15bde5b726c45f6444 Mon Sep 17 00:00:00 2001 From: Marius <38134006+realmayus@users.noreply.github.com> Date: Thu, 10 Oct 2024 18:59:32 +0200 Subject: [PATCH 1/4] Feat: Add /logout route, redirect there from logout button (#574) Add /logout route, redirect there from logout button Closes https://github.com/corona-school/project-user/issues/1234 --- src/components/Logout.tsx | 37 +++++++++++++++++++ src/hooks/useLogout.ts | 27 -------------- src/modals/DeactivateAccountModal.tsx | 7 +--- src/pages/Settings.tsx | 4 +- src/routing/Navigator.tsx | 3 +- .../RequireScreeningSettingsDropdown.tsx | 6 +-- 6 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 src/components/Logout.tsx delete mode 100644 src/hooks/useLogout.ts diff --git a/src/components/Logout.tsx b/src/components/Logout.tsx new file mode 100644 index 000000000..59d56d478 --- /dev/null +++ b/src/components/Logout.tsx @@ -0,0 +1,37 @@ +import useApollo from '../hooks/useApollo'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useCallback, useContext } from 'react'; +import { WebPushContext } from '@/context/WebPushProvider'; +import { WEBPUSH_ACTIVE } from '@/config'; +import { logError } from '@/log'; +import CenterLoadingSpinner from '@/components/CenterLoadingSpinner'; + +export default function Logout() { + const navigate = useNavigate(); + const location = useLocation(); + const locState = location.state as { deactivated?: boolean }; + + const useLogout = () => { + const { logout } = useApollo(); + const { unsubscribe } = useContext(WebPushContext); + return useCallback(async () => { + if (WEBPUSH_ACTIVE) { + try { + await unsubscribe(); + } catch (error) { + logError('WebPush', 'Failed to unsubscribe', error); + } + } + try { + await logout(); + } catch (error) { + logError('Authentication', 'Failed to logout', error); + } + }, []); + }; + + const logout = useLogout(); + logout().then(() => navigate('/welcome', { state: locState })); + + return ; +} diff --git a/src/hooks/useLogout.ts b/src/hooks/useLogout.ts deleted file mode 100644 index 73d56e87d..000000000 --- a/src/hooks/useLogout.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback, useContext } from 'react'; -import { WebPushContext } from '../context/WebPushProvider'; -import useApollo from './useApollo'; -import { logError } from '../log'; -import { WEBPUSH_ACTIVE } from '../config'; - -const useLogout = () => { - const { logout } = useApollo(); - const { unsubscribe } = useContext(WebPushContext); - const execute = useCallback(async () => { - if (WEBPUSH_ACTIVE) { - try { - await unsubscribe(); - } catch (error) { - logError('WebPush', 'Failed to unsubscribe', error); - } - } - try { - await logout(); - } catch (error) { - logError('Authentication', 'Failed to logout', error); - } - }, []); - return execute; -}; - -export default useLogout; diff --git a/src/modals/DeactivateAccountModal.tsx b/src/modals/DeactivateAccountModal.tsx index 61fbe8146..8c35a3de7 100644 --- a/src/modals/DeactivateAccountModal.tsx +++ b/src/modals/DeactivateAccountModal.tsx @@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { useUserType } from '../hooks/useApollo'; import DisableableButton from '../components/DisablebleButton'; -import useLogout from '../hooks/useLogout'; // corresponding dissolve reason ids in translation file // for now just loop through 0-5 and 0-6 (+1 in loop) @@ -25,7 +24,6 @@ const DeactivateAccountModal: React.FC = ({ isOpen, onCloseModal }) => { const { space } = useTheme(); const navigate = useNavigate(); const { trackEvent } = useMatomo(); - const logout = useLogout(); const { t } = useTranslation(); const userType = useUserType(); @@ -76,15 +74,14 @@ const DeactivateAccountModal: React.FC = ({ isOpen, onCloseModal }) => { name: 'Account deaktivieren', documentTitle: 'Deactivate', }); - logout(); - navigate('/welcome', { state: { deactivated: true } }); + navigate('/logout', { state: { deactivated: true } }); } else { showError(); } } catch (e) { showError(); } - }, [reason, deactivateAccount, isOther, t, userType, other, onCloseModal, trackEvent, logout, navigate, toast]); + }, [reason, deactivateAccount, isOther, t, userType, other, onCloseModal, trackEvent, navigate, toast]); const isValidInput = useMemo(() => { if (!reason) return false; diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 8d54a2a8f..1a54bd341 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,14 +11,12 @@ import ProfileSettingRow from '../widgets/ProfileSettingRow'; import { SwitchLanguageModal } from '../modals/SwitchLanguageModal'; import { GAMIFICATION_ACTIVE } from '../config'; import { InstallationContext } from '../context/InstallationProvider'; -import useLogout from '../hooks/useLogout'; const Settings: React.FC = () => { const { space, sizes } = useTheme(); const { t } = useTranslation(); const navigate = useNavigate(); const { user } = useApollo(); - const logout = useLogout(); const tabspace = 3; const { trackPageView, trackEvent } = useMatomo(); const userType = useUserType(); @@ -112,7 +110,7 @@ const Settings: React.FC = () => { name: 'Abmelden im Account', documentTitle: 'Logout', }); - logout(); + navigate('/logout'); }} /> diff --git a/src/routing/Navigator.tsx b/src/routing/Navigator.tsx index f697853b1..b4dbb79d7 100644 --- a/src/routing/Navigator.tsx +++ b/src/routing/Navigator.tsx @@ -10,6 +10,7 @@ import LoginToken from '../pages/LoginToken'; import { RequireAuth } from '../User'; import FullPageModal from '../modals/FullPageModal'; import { lazyWithRetry } from '../lazy'; +import Logout from '../components/Logout'; // All other pages load lazy: const NavigatorLazy = lazyWithRetry(() => import('./NavigatorLazy'), { prefetch: true }); @@ -25,7 +26,7 @@ export default function Navigator() { } /> } /> - + } /> } /> { const { t } = useTranslation(); const [isNotificationPrefencesOpen, setIsNotificationPreferencesOpen] = useState(false); const [isDeactivateAccountOpen, setIsDeactivateAccountOpen] = useState(false); const [isContactSupportOpen, setIsContactSupportOpen] = useState(false); - const logout = useLogout(); + const navigate = useNavigate(); return ( <> { setIsContactSupportOpen(true)}>{t('requireScreening.contactSupport')} setIsDeactivateAccountOpen(true)}>{t('settings.account.deactivateAccount')} - logout()}>{t('logout')} + navigate('/logout')}>{t('logout')} setIsDeactivateAccountOpen(false)} /> setIsNotificationPreferencesOpen(false)} /> From 86698145bd94ffe9b6a31f7a6c648b052554fb3f Mon Sep 17 00:00:00 2001 From: John Angel <161815068+JeangelLF@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:17:06 +0200 Subject: [PATCH 2/4] Feat: Reschedule declined match meetings (#677) ## Ticket https://github.com/corona-school/project-user/issues/1303 ## What was done? - Added clear indication when a SuS declined a match appointment - Added clearer indication when an appointment is in progress - Added information about rescheduling a declined match appointment ## Preview https://github.com/user-attachments/assets/bb942fa6-08c4-4e24-aafd-b8432240e7a6 Bildschirmfoto 2024-10-08 um 18 27 47 --- src/assets/icons/lernfair/icon_achtung.svg | 5 -- .../appointment/AppointmentDetail.tsx | 72 ++++++++++++++++--- .../appointment/AppointmentMetaDetails.tsx | 33 +++++---- src/components/appointment/Buttons.tsx | 71 ------------------ src/lang/ar.json | 8 ++- src/lang/de.json | 8 ++- src/lang/en.json | 8 ++- src/lang/ru.json | 8 ++- src/lang/tr.json | 8 ++- src/lang/uk.json | 8 ++- src/pages/SingleMatch.tsx | 2 + src/widgets/AppointmentDay.tsx | 8 +++ src/widgets/AppointmentList.tsx | 1 + src/widgets/AppointmentTile.tsx | 42 ++++++++--- 14 files changed, 161 insertions(+), 121 deletions(-) delete mode 100644 src/assets/icons/lernfair/icon_achtung.svg delete mode 100644 src/components/appointment/Buttons.tsx diff --git a/src/assets/icons/lernfair/icon_achtung.svg b/src/assets/icons/lernfair/icon_achtung.svg deleted file mode 100644 index 9ead96be8..000000000 --- a/src/assets/icons/lernfair/icon_achtung.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/components/appointment/AppointmentDetail.tsx b/src/components/appointment/AppointmentDetail.tsx index f1a912623..4a40cbe91 100644 --- a/src/components/appointment/AppointmentDetail.tsx +++ b/src/components/appointment/AppointmentDetail.tsx @@ -6,7 +6,6 @@ import AppointmentMetaDetails from './AppointmentMetaDetails'; import Header from './Header'; import Avatars from './Avatars'; import Description from './Description'; -import Buttons from './Buttons'; import { DateTime } from 'luxon'; import { useMutation } from '@apollo/client'; import useApollo from '../../hooks/useApollo'; @@ -15,6 +14,9 @@ import RejectAppointmentModal, { RejectType } from '../../modals/RejectAppointme import { gql } from '../../gql'; import { Lecture_Appointmenttype_Enum } from '../../gql/graphql'; import { PUPIL_APPOINTMENT } from '../../pages/Appointment'; +import { Typography } from '../Typography'; +import { IconInfoCircle, IconClockEdit, IconTrash, IconPencil } from '@tabler/icons-react'; +import { Button } from '../Button'; type AppointmentDetailProps = { appointment: Appointment; @@ -110,6 +112,12 @@ const AppointmentDetail: React.FC = ({ appointment }) => () => (appointment.appointmentType === Lecture_Appointmenttype_Enum.Group && appointment.total === 1 ? true : false), [appointment.total] ); + + const wasRejected = !!appointment.participants?.every((e) => appointment.declinedBy?.includes(e.userID!)); + const byMatch = !appointment.declinedBy?.includes(user?.userID!); + const wasRejectedByMe = appointment.declinedBy?.includes(user?.userID!); + const wasRejectedByMatch = appointment.appointmentType === 'match' && wasRejected && byMatch; + return ( <> setShowDeleteModal(false)}> @@ -139,15 +147,61 @@ const AppointmentDetail: React.FC = ({ appointment }) => overrideMeetingLink={appointment.override_meeting_link} zoomMeetingUrl={appointment.zoomMeetingUrl} /> + {wasRejectedByMatch && ( + <> +
+ + {t('appointment.detail.cancelledBy', { name: appointment.displayName })} +
+ {t('appointment.detail.rescheduleDescription', { name: appointment.displayName })} + + )} - - setShowDeclineModal(true) : () => setShowDeleteModal(true)} - onEditPress={() => navigate(`/edit-appointment/${appointment.id}`)} - canceled={(appointment.declinedBy?.includes(user?.userID ?? '') ?? false) || canceled} - isOver={isPastAppointment} - isLast={isLastAppointment} - /> +
+ {user?.student && ( + <> + + + + )} + {user?.pupil && ( + + )} +
); diff --git a/src/components/appointment/AppointmentMetaDetails.tsx b/src/components/appointment/AppointmentMetaDetails.tsx index ef8bc0082..2e970d7e9 100644 --- a/src/components/appointment/AppointmentMetaDetails.tsx +++ b/src/components/appointment/AppointmentMetaDetails.tsx @@ -123,21 +123,24 @@ const AppointmentMetaDetails: React.FC = ({ )} - {appointmentId && appointmentType && ( - <> - - - )} +
+ {appointmentId && appointmentType && ( + <> + + + )} +
); }; diff --git a/src/components/appointment/Buttons.tsx b/src/components/appointment/Buttons.tsx deleted file mode 100644 index 09571583d..000000000 --- a/src/components/appointment/Buttons.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { Stack, useBreakpointValue } from 'native-base'; -import { useTranslation } from 'react-i18next'; -import useApollo from '../../hooks/useApollo'; -import { useLayoutHelper } from '../../hooks/useLayoutHelper'; -import DisableableButton from '../DisablebleButton'; - -type AvatarsProps = { - onPress: () => void; - onEditPress: () => void; - canceled: boolean; - isOver: boolean; - isLast: boolean; -}; -const Buttons: React.FC = ({ onPress, onEditPress, canceled, isOver, isLast }) => { - const { isMobile } = useLayoutHelper(); - const { t } = useTranslation(); - const { user } = useApollo(); - - const buttonWidth = useBreakpointValue({ - base: 'full', - lg: '300', - }); - - return ( - <> - - {user?.student && ( - <> - - {t('appointment.detail.deleteButton')} - - - {t('appointment.detail.editButton')} - - - )} - {user?.pupil && ( - - {t('appointment.detail.cancelButton')} - - )} - - - ); -}; - -export default Buttons; diff --git a/src/lang/ar.json b/src/lang/ar.json index 245faaabb..5d4105fd5 100644 --- a/src/lang/ar.json +++ b/src/lang/ar.json @@ -33,7 +33,8 @@ }, "appointmentTile": { "lecture": "الدرس #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "تم إلغاؤه بواسطة {{name}}" }, "create": { "videoSelectOptions": { @@ -101,7 +102,10 @@ "editButton": "تعديل الموعد", "canceledToast": "تم إلغاء الموعد", "zoomTooltipStudent": "إذا قمت بالنقر على هذا الرابط، ستنضم إلى الاجتماع كمشارك. للانضمام إلى الاجتماع كمضيف، استخدم زر \"الانضمام إلى دردشة الفيديو الآن\".", - "zoomTooltipPupil": "لا تشارك هذا الرابط مع أشخاص آخرين. هذه هي الطريقة الوحيدة التي يمكننا من خلالها ضمان أمن المنصة." + "zoomTooltipPupil": "لا تشارك هذا الرابط مع أشخاص آخرين. هذه هي الطريقة الوحيدة التي يمكننا من خلالها ضمان أمن المنصة.", + "cancelledBy": "موعد {{name}} ألغيت هذه", + "rescheduleButton": "موعد المناوبة", + "rescheduleDescription": "موعد إذا قمت بتحريك، سيتم تحميل {{name}}} مرة أخرى." }, "zoomModal": { "useZoomApp": { diff --git a/src/lang/de.json b/src/lang/de.json index 28f6d8a20..81a02a425 100644 --- a/src/lang/de.json +++ b/src/lang/de.json @@ -59,7 +59,8 @@ }, "appointmentTile": { "lecture": "Lektion #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "Abgesagt von {{name}}" }, "create": { "assignmentHeader": "Für welches Lernangebot soll dieser Termin erstellt werden?", @@ -112,6 +113,7 @@ "deleteButton": "Termin löschen", "cancelButton": "Termin absagen", "editButton": "Termin bearbeiten", + "rescheduleButton": "Termin verschieben", "canceledToast": "Termin wurde abgesagt", "zoomTooltipStudent": "Wenn du diesen Link aufrufst, trittst du dem Meeting als Teilnehmer:in bei. Um als Host dem Meeting beizutrteten nutze den Button \"Jetzt Videochat beitreten\".", "zoomTooltipPupil": "Teile diesen Link niemals mit anderen Personen. Nur so können wir die Sicherheit der Plattform gewährleisten.", @@ -127,7 +129,9 @@ "isOver": "Du kannst diesen Termin nicht absagen, da er bereits vorbei ist.", "isCancelled": "Du hast diesen Termin bereits abgesagt." } - } + }, + "cancelledBy": "{{name}} hat diesen Termin abgesagt", + "rescheduleDescription": "Wenn du den Termin verschiebst, wird {{name}} wieder eingeladen." }, "zoomModal": { "header": "Hinweise zum Videochat", diff --git a/src/lang/en.json b/src/lang/en.json index 8d3535353..b99141831 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -33,7 +33,8 @@ }, "appointmentTile": { "lecture": "Lesson #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "Canceled by {{name}}" }, "create": { "videoSelectOptions": { @@ -101,7 +102,10 @@ "editButton": "Edit lecture", "canceledToast": "Lecture was canceled", "zoomTooltipStudent": "If you click on this link, you will join the meeting as a participant. To join the meeting as a host, use the \"Join video chat now\" button.", - "zoomTooltipPupil": "Never share this link with other people. This is the only way we can guarantee the security of the platform." + "zoomTooltipPupil": "Never share this link with other people. This is the only way we can guarantee the security of the platform.", + "cancelledBy": "{{name}} has canceled this lecture ", + "rescheduleButton": "Reschedule appointment", + "rescheduleDescription": "If you reschedule the lecture, {{name}} will be invited again." }, "zoomModal": { "useZoomApp": { diff --git a/src/lang/ru.json b/src/lang/ru.json index 21ecc1591..760394417 100644 --- a/src/lang/ru.json +++ b/src/lang/ru.json @@ -33,7 +33,8 @@ }, "appointmentTile": { "lecture": "Урок #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "Отменено {{name}}" }, "create": { "videoSelectOptions": { @@ -101,7 +102,10 @@ "editButton": "Редактировать назначение", "canceledToast": "Назначение было отменено", "zoomTooltipStudent": "участник Если вы нажмете на эту ссылку, вы присоединитесь к встрече в качестве . Чтобы присоединиться к встрече в качестве ведущего, воспользуйтесь кнопкой \"Присоединиться к видеочату сейчас\".", - "zoomTooltipPupil": "Никогда не передавайте эту ссылку другим людям. Только так мы можем гарантировать безопасность платформы." + "zoomTooltipPupil": "Никогда не передавайте эту ссылку другим людям. Только так мы можем гарантировать безопасность платформы.", + "cancelledBy": "{{name}} отменил эту встречу", + "rescheduleButton": "Отложить встречу", + "rescheduleDescription": "Если вы отложите встречу, {{name}} будет приглашен снова." }, "zoomModal": { "useZoomApp": { diff --git a/src/lang/tr.json b/src/lang/tr.json index 64bf82403..fb23ca3c0 100644 --- a/src/lang/tr.json +++ b/src/lang/tr.json @@ -33,7 +33,8 @@ }, "appointmentTile": { "lecture": "Ders #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "{{name}} tarafından iptal edildi" }, "create": { "videoSelectOptions": { @@ -101,7 +102,10 @@ "editButton": "Randevu düzenleme", "canceledToast": "Randevu iptal edildi", "zoomTooltipStudent": "Bu bağlantıya tıklarsanız, toplantıya katılımcı olarak katılırsınız. Toplantıya ev sahibi olarak katılmak için \"Video sohbete şimdi katıl\" düğmesini kullanın.", - "zoomTooltipPupil": "Bu bağlantıyı asla başkalarıyla paylaşmayın. Platformun güvenliğini ancak bu şekilde garanti edebiliriz." + "zoomTooltipPupil": "Bu bağlantıyı asla başkalarıyla paylaşmayın. Platformun güvenliğini ancak bu şekilde garanti edebiliriz.", + "cancelledBy": "{{name}} bu randevuyu iptal etti", + "rescheduleButton": "Randevuyu ertele", + "rescheduleDescription": "Randevuyu ertelerseniz, {{name}} tekrar davet edilecektir." }, "zoomModal": { "useZoomApp": { diff --git a/src/lang/uk.json b/src/lang/uk.json index b91b4ab4b..eb621cb5e 100644 --- a/src/lang/uk.json +++ b/src/lang/uk.json @@ -33,7 +33,8 @@ }, "appointmentTile": { "lecture": "Урок #{{position}}", - "title": ": {{appointmentTitle}}" + "title": ": {{appointmentTitle}}", + "cancelledBy": "Скасовано {{name}}" }, "create": { "videoSelectOptions": { @@ -101,7 +102,10 @@ "editButton": "Редагувати зустріч", "canceledToast": "Зустріч було скасовано", "zoomTooltipStudent": "Якщо ви натиснете на це посилання, ви приєднаєтеся до зустрічі як учасник. Щоб приєднатися до зустрічі як організатор, скористайтеся кнопкою \"Приєднатися до відеочату зараз\".", - "zoomTooltipPupil": "Ніколи не діліться цим посиланням з іншими людьми. Це єдиний спосіб, яким ми можемо гарантувати безпеку платформи." + "zoomTooltipPupil": "Ніколи не діліться цим посиланням з іншими людьми. Це єдиний спосіб, яким ми можемо гарантувати безпеку платформи.", + "cancelledBy": "{{name}} скасував цю зустріч", + "rescheduleButton": "Перенести дату", + "rescheduleDescription": "Якщо ви відкладете зустріч, {{name}} буде запрошено знову." }, "zoomModal": { "useZoomApp": { diff --git a/src/pages/SingleMatch.tsx b/src/pages/SingleMatch.tsx index 40c9e936b..6aa78d1bb 100644 --- a/src/pages/SingleMatch.tsx +++ b/src/pages/SingleMatch.tsx @@ -80,12 +80,14 @@ query SingleMatchAppointments_NO_CACHE($matchId: Int!, $take: Float!, $skip: Flo isOrganizer isParticipant override_meeting_link + declinedBy organizers(skip: 0, take: 5) { id firstname lastname } participants(skip: 0, take: 10) { + userID id firstname lastname diff --git a/src/widgets/AppointmentDay.tsx b/src/widgets/AppointmentDay.tsx index 35027491e..6af3bf3a4 100644 --- a/src/widgets/AppointmentDay.tsx +++ b/src/widgets/AppointmentDay.tsx @@ -24,6 +24,7 @@ type Props = { displayName: Appointment['displayName']; appointmentId: Appointment['id']; canJoinVideochat?: boolean; + declinedBy: Appointment['declinedBy']; }; export const canJoinMeeting = (start: string, duration: number, joinBeforeMinutes: number, now: DateTime): boolean => { @@ -49,6 +50,7 @@ const AppointmentDay: React.FC = ({ displayName, appointmentId, canJoinVideochat, + declinedBy, }) => { const isCurrentMonth = useCallback((start: string): boolean => { const now = DateTime.now(); @@ -81,6 +83,8 @@ const AppointmentDay: React.FC = ({ lg: '100%', }); + const wasRejected = !!participants?.every((e) => declinedBy?.includes(e.userID!)); + return ( <> {!isReadOnly && organizers && participants ? ( @@ -103,6 +107,8 @@ const AppointmentDay: React.FC = ({ isOrganizer={isOrganizer} displayName={displayName} appointmentId={appointmentId} + wasRejected={wasRejected} + declinedBy={declinedBy} /> @@ -124,6 +130,8 @@ const AppointmentDay: React.FC = ({ displayName={displayName} isReadOnly={isReadOnly} appointmentId={appointmentId} + wasRejected={wasRejected} + declinedBy={declinedBy} /> diff --git a/src/widgets/AppointmentList.tsx b/src/widgets/AppointmentList.tsx index a028340d3..608bdc5e0 100644 --- a/src/widgets/AppointmentList.tsx +++ b/src/widgets/AppointmentList.tsx @@ -158,6 +158,7 @@ const AppointmentList: React.FC = ({ isOrganizer={appointment.isOrganizer} displayName={appointment.displayName} appointmentId={appointment.id} + declinedBy={appointment.declinedBy} /> diff --git a/src/widgets/AppointmentTile.tsx b/src/widgets/AppointmentTile.tsx index a7515bacf..648ec8201 100644 --- a/src/widgets/AppointmentTile.tsx +++ b/src/widgets/AppointmentTile.tsx @@ -1,5 +1,4 @@ import { Box, Card, HStack, VStack, Text, Avatar, Heading, useBreakpointValue, Spacer } from 'native-base'; -import WarningIcon from '../assets/icons/lernfair/icon_achtung.svg'; import StudentAvatar from '../assets/icons/lernfair/avatar_student.svg'; import PupilAvatar from '../assets/icons/lernfair/avatar_pupil.svg'; import { Pressable } from 'react-native'; @@ -7,6 +6,10 @@ import { AppointmentParticipant, Organizer } from '../gql/graphql'; import { useTranslation } from 'react-i18next'; import { Appointment } from '../types/lernfair/Appointment'; import VideoButton from '../components/VideoButton'; +import { IconInfoCircle, IconPointFilled } from '@tabler/icons-react'; +import { Typography } from '@/components/Typography'; +import { cn } from '@/lib/Tailwind'; +import { useUser } from '@/hooks/useApollo'; type Props = { timeDescriptionText: string; @@ -24,6 +27,8 @@ type Props = { displayName: Appointment['displayName']; appointmentId?: Appointment['id']; canJoinVideochat?: boolean; + wasRejected: boolean; + declinedBy: Appointment['declinedBy']; }; const AppointmentTile: React.FC = ({ @@ -40,32 +45,43 @@ const AppointmentTile: React.FC = ({ appointmentId, appointmentType, isOrganizer, + wasRejected, + declinedBy, }) => { const { t } = useTranslation(); const width = useBreakpointValue({ base: '100%', lg: isFullWidth ? '92%' : '90%', }); + const { userID } = useUser(); const buttonWidth = useBreakpointValue({ base: 'full', lg: '300', }); + + const byMatch = !declinedBy?.includes(userID); + const isHighlighted = !isReadOnly && isCurrentlyTakingPlace && !wasRejected; + const wasRejectedByMatch = appointmentType === 'match' && wasRejected && byMatch; + return ( - + - {!isReadOnly && isCurrentlyTakingPlace && ( + {isHighlighted && ( - +
+ + +
)} - + {timeDescriptionText} - +
{organizers && participants && ( @@ -87,18 +103,26 @@ const AppointmentTile: React.FC = ({ )}
- + {displayName} {position && ( - + {t('appointment.appointmentTile.lecture', { position: position }) + (title ? t('appointment.appointmentTile.title', { appointmentTitle: title }) : '')} )} + {wasRejectedByMatch && ( +
+ + + {t('appointment.appointmentTile.cancelledBy', { name: displayName })} + +
+ )}
- {!isReadOnly && isCurrentlyTakingPlace && appointmentId && appointmentType && ( + {isHighlighted && appointmentId && appointmentType && ( Date: Mon, 14 Oct 2024 11:40:31 +0200 Subject: [PATCH 3/4] feat: Allow clickable emails on cooperation card (#678) ## What was done? - Added utility to render emails as anchors in cooperation card --- src/Utility.ts | 6 ++++++ src/pages/registration/PersonalData.tsx | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Utility.ts b/src/Utility.ts index 611b96aaf..44ebe5125 100644 --- a/src/Utility.ts +++ b/src/Utility.ts @@ -155,6 +155,11 @@ export const sortByDate = { + const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g; + return text.replace(emailRegex, '$1'); +}; + const Utility = { createToken, toTimerString, @@ -165,5 +170,6 @@ const Utility = { handleDateString, getTrafficStatus, sortByDate, + renderTextWithEmailLinks, }; export default Utility; diff --git a/src/pages/registration/PersonalData.tsx b/src/pages/registration/PersonalData.tsx index 470c43d10..36276ceb1 100644 --- a/src/pages/registration/PersonalData.tsx +++ b/src/pages/registration/PersonalData.tsx @@ -11,6 +11,8 @@ import isEmail from 'validator/es/lib/isEmail'; import { Cooperation } from '../../gql/graphql'; import { InfoCard } from '../../components/InfoCard'; import { usePageTitle } from '../../hooks/usePageTitle'; +import { Typography } from '@/components/Typography'; +import { renderTextWithEmailLinks } from '@/Utility'; export default function PersonalData({ cooperation }: { cooperation?: Cooperation }) { const { @@ -74,7 +76,13 @@ export default function PersonalData({ cooperation }: { cooperation?: Cooperatio return ( - {cooperation && } + {cooperation && ( + + + + + + )} From 867cb6d88bc0612b6c3b7ed0489a2ced4197897b Mon Sep 17 00:00:00 2001 From: John Angel <161815068+JeangelLF@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:22:53 +0200 Subject: [PATCH 4/4] Feat: Add back greeting on dashboard page (#679) ## Ticket Partially tackles https://github.com/corona-school/project-user/issues/1302 ## What was done? - Added back the greeting message but this time only on the Dashboard page Bildschirmfoto 2024-10-14 um 12 22 27 --- src/pages/pupil/Dashboard.tsx | 6 ++++++ src/pages/pupil/ProfilePupil.tsx | 27 -------------------------- src/pages/student/DashboardStudent.tsx | 6 ++++++ src/pages/student/ProfileStudent.tsx | 16 --------------- 4 files changed, 12 insertions(+), 43 deletions(-) diff --git a/src/pages/pupil/Dashboard.tsx b/src/pages/pupil/Dashboard.tsx index 80d5e8a18..de10f2c8c 100644 --- a/src/pages/pupil/Dashboard.tsx +++ b/src/pages/pupil/Dashboard.tsx @@ -22,6 +22,7 @@ import { Lecture } from '../../gql/graphql'; import DisableableButton from '../../components/DisablebleButton'; import { useRoles } from '../../hooks/useApollo'; import ConfirmationModal from '@/modals/ConfirmationModal'; +import { Typography } from '@/components/Typography'; type Props = {}; @@ -220,6 +221,11 @@ const Dashboard: React.FC = () => { {!called || (loading && )} {called && !loading && ( +
+ + {t('hallo')} {data?.me.firstname}  👋 + +
diff --git a/src/pages/pupil/ProfilePupil.tsx b/src/pages/pupil/ProfilePupil.tsx index f31cf5698..b5b591344 100644 --- a/src/pages/pupil/ProfilePupil.tsx +++ b/src/pages/pupil/ProfilePupil.tsx @@ -223,33 +223,6 @@ const ProfilePupil: React.FC = () => { hideMenu={isMobileSM} previousFallbackRoute="/settings" headerTitle={t('profile.title')} - headerContent={ - - - - - {data?.me?.firstname} - - - - } headerLeft={ !isMobileSM && ( diff --git a/src/pages/student/DashboardStudent.tsx b/src/pages/student/DashboardStudent.tsx index d90f62bcd..8028f12b3 100644 --- a/src/pages/student/DashboardStudent.tsx +++ b/src/pages/student/DashboardStudent.tsx @@ -26,6 +26,7 @@ import NextAppointmentCard from '../../widgets/NextAppointmentCard'; import { Lecture } from '../../gql/graphql'; import useApollo from '../../hooks/useApollo'; import { useUserType } from '../../hooks/useApollo'; +import { Typography } from '@/components/Typography'; type Props = {}; @@ -228,6 +229,11 @@ const DashboardStudent: React.FC = () => { {!called || (loading && )} {called && !loading && ( +
+ + {t('hallo')} {data?.me.firstname}  👋 + +
diff --git a/src/pages/student/ProfileStudent.tsx b/src/pages/student/ProfileStudent.tsx index 29d70bb4b..806ff5d67 100644 --- a/src/pages/student/ProfileStudent.tsx +++ b/src/pages/student/ProfileStudent.tsx @@ -168,22 +168,6 @@ const ProfileStudent: React.FC = () => { isLoading={loading} previousFallbackRoute="/settings" headerTitle={t('profile.title')} - headerContent={ - - - {data?.me?.firstname} - - - } headerLeft={ !isMobileSM && (