From f036d409e60a39fd1d3cb2f0db53a6056615f336 Mon Sep 17 00:00:00 2001 From: Abdulrahman Alshahrani <62489819+Abdomash@users.noreply.github.com> Date: Sat, 8 Mar 2025 15:41:09 -0600 Subject: [PATCH] feat: implement a What's New prompt (#539) * feat: create whats new initial component * feat: create initial whats-new hook * feat: create whats-new story * feat: add button to open dialog in storybook * feat: complete popup ui * feat: add check for new updates or installs * fix: fix linter issues * fix: use proper features and add video * fix: properly fetch version from manifest * feat: add a link to open the popup * fix: update spacing and features' content * fix: update UTRP Map name * fix: increase icon size and display version correctly * feat: update the features video * fix: update offwhite color * fix: color typo * fix: fixing colors again * feat: use numbers instead of boolean * fix: typo in import * feat: add type safety to features array * feat: cdn video url * fix: delete mp4 video * feat: handle video failure to load * fix: make border outline tight to video * feat: make design responsive * fix: make features array readonly --------- Co-authored-by: doprz <52579214+doprz@users.noreply.github.com> Co-authored-by: Derek Chen --- src/pages/calendar/CalendarMain.tsx | 2 + src/shared/storage/ExtensionStore.ts | 3 + .../components/WhatsNewPopup.stories.tsx | 38 +++++ src/views/components/calendar/Calendar.tsx | 43 +++-- src/views/components/common/WhatsNewPopup.tsx | 148 ++++++++++++++++++ src/views/hooks/useWhatsNew.tsx | 46 ++++++ 6 files changed, 267 insertions(+), 13 deletions(-) create mode 100644 src/stories/components/WhatsNewPopup.stories.tsx create mode 100644 src/views/components/common/WhatsNewPopup.tsx create mode 100644 src/views/hooks/useWhatsNew.tsx diff --git a/src/pages/calendar/CalendarMain.tsx b/src/pages/calendar/CalendarMain.tsx index 56837873..4fcce1bb 100644 --- a/src/pages/calendar/CalendarMain.tsx +++ b/src/pages/calendar/CalendarMain.tsx @@ -3,6 +3,7 @@ import Calendar from '@views/components/calendar/Calendar'; import DialogProvider from '@views/components/common/DialogProvider/DialogProvider'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import { MigrationDialog } from '@views/components/common/MigrationDialog'; +import { WhatsNewDialog } from '@views/components/common/WhatsNewPopup'; import SentryProvider from '@views/contexts/SentryContext'; import { MessageListener } from 'chrome-extension-toolkit'; import useKC_DABR_WASM from 'kc-dabr-wasm'; @@ -34,6 +35,7 @@ export default function CalendarMain() { + diff --git a/src/shared/storage/ExtensionStore.ts b/src/shared/storage/ExtensionStore.ts index cd12660c..197b52e4 100644 --- a/src/shared/storage/ExtensionStore.ts +++ b/src/shared/storage/ExtensionStore.ts @@ -8,11 +8,14 @@ interface IExtensionStore { version: string; /** When was the last update */ lastUpdate: number; + /** The last version of the "What's New" popup that was shown to the user */ + lastWhatsNewPopupVersion: number; } export const ExtensionStore = createLocalStore({ version: chrome.runtime.getManifest().version, lastUpdate: Date.now(), + lastWhatsNewPopupVersion: 0, }); debugStore({ ExtensionStore }); diff --git a/src/stories/components/WhatsNewPopup.stories.tsx b/src/stories/components/WhatsNewPopup.stories.tsx new file mode 100644 index 00000000..03472305 --- /dev/null +++ b/src/stories/components/WhatsNewPopup.stories.tsx @@ -0,0 +1,38 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '@views/components/common/Button'; +import DialogProvider from '@views/components/common/DialogProvider/DialogProvider'; +import WhatsNewPopup from '@views/components/common/WhatsNewPopup'; +import useWhatsNewPopUp from '@views/hooks/useWhatsNew'; +import React from 'react'; + +const meta = { + title: 'Components/Common/WhatsNewPopup', + component: WhatsNewPopup, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { + render: () => ( + + + + ), +}; + +const InnerComponent = () => { + const handleOnClick = useWhatsNewPopUp(); + + return ( + + ); +}; diff --git a/src/views/components/calendar/Calendar.tsx b/src/views/components/calendar/Calendar.tsx index c106918b..0715cf75 100644 --- a/src/views/components/calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar.tsx @@ -13,6 +13,7 @@ import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalog import { CalendarContext } from '@views/contexts/CalendarContext'; import useCourseFromUrl from '@views/hooks/useCourseFromUrl'; import { useFlattenedCourseSchedule } from '@views/hooks/useFlattenedCourseSchedule'; +import useWhatsNewPopUp from '@views/hooks/useWhatsNew'; import { MessageListener } from 'chrome-extension-toolkit'; import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; @@ -34,6 +35,7 @@ export default function Calendar(): JSX.Element { const [showPopup, setShowPopup] = useState(course !== null); const [showSidebar, setShowSidebar] = useState(true); + const showWhatsNewDialog = useWhatsNewPopUp(); useEffect(() => { const listener = new MessageListener({ @@ -99,19 +101,34 @@ export default function Calendar(): JSX.Element { {/* */} - { - event.preventDefault(); - openReportWindow(); - }} - > - Send us Feedback! - - + diff --git a/src/views/components/common/WhatsNewPopup.tsx b/src/views/components/common/WhatsNewPopup.tsx new file mode 100644 index 00000000..db6ee9d0 --- /dev/null +++ b/src/views/components/common/WhatsNewPopup.tsx @@ -0,0 +1,148 @@ +import type { IconProps } from '@phosphor-icons/react'; +import { CloudX, Copy, Exam, MapPinArea, Palette } from '@phosphor-icons/react'; +import { ExtensionStore } from '@shared/storage/ExtensionStore'; +import Text from '@views/components/common/Text/Text'; +import useWhatsNewPopUp from '@views/hooks/useWhatsNew'; +import React, { useEffect, useState } from 'react'; + +/** + * This is the version of the 'What's New' features popup. + * + * It is used to check if the popup has already been shown to the user or not + * + * It should be incremented every time the "What's New" popup is updated. + */ +const WHATSNEW_POPUP_VERSION = 1; + +const WHATSNEW_VIDEO_URL = 'https://cdn.longhorns.dev/whats-new-v2.1.2.mp4'; + +type Feature = { + id: string; + icon: React.ForwardRefExoticComponent; + title: string | JSX.Element; + description: string; +}; + +const NEW_FEATURES = [ + { + id: 'custom-course-colors', + icon: Palette, + title: 'Custom Course Colors', + description: 'Paint your schedule in your favorite color theme', + }, + { + id: 'quick-copy', + icon: Copy, + title: 'Quick Copy', + description: 'Quickly copy a course unique number to your clipboard', + }, + { + id: 'updated-grades', + icon: Exam, + title: 'Updated Grades', + description: 'Fall 2024 grades are now available in the grade distribution', + }, + { + id: 'ut-map', + icon: MapPinArea, + title: ( +
+ UTRP Map + + BETA + +
+ ), + description: 'Find directions to your classes with our beta map feature in the settings page', + }, +] as const satisfies readonly Feature[]; + +/** + * WhatsNewPopupContent component. + * + * This component displays the content of the WhatsNew dialog. + * It shows the new features that have been added to the extension. + * + * @returns A JSX of WhatsNewPopupContent component. + */ +export default function WhatsNewPopupContent(): JSX.Element { + const [videoError, setVideoError] = useState(false); + + return ( +
+
+
+ {NEW_FEATURES.map(({ id, icon: Icon, title, description }) => ( +
+ +
+ + {title} + + + {description} + +
+
+ ))} +
+
+ {videoError ? ( +
+
+ + + Failed to load video. Please try again later. + +
+
+ ) : ( + + )} +
+
+
+ ); +} + +/** + * WhatsNewDialog component. + * + * This component is responsible for checking if the extension has already been updated + * and if so, it displays the WhatsNew dialog. Then it updates the state to show that the + * dialog has been shown. + * + * @returns An empty fragment. + * + * @remarks + * The component uses the `useWhatsNew` hook to show the WhatsNew dialog and the + * `useEffect` hook to perform the check on component mount. It also uses the `ExtensionStore` + * to view the state of the dialog. + */ +export function WhatsNewDialog(): null { + const showPopUp = useWhatsNewPopUp(); + + useEffect(() => { + const checkUpdate = async () => { + const version = await ExtensionStore.get('lastWhatsNewPopupVersion'); + if (version !== WHATSNEW_POPUP_VERSION) { + await ExtensionStore.set('lastWhatsNewPopupVersion', WHATSNEW_POPUP_VERSION); + showPopUp(); + } + }; + + checkUpdate(); + + // This is on purpose + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return null; +} diff --git a/src/views/hooks/useWhatsNew.tsx b/src/views/hooks/useWhatsNew.tsx new file mode 100644 index 00000000..d803cc87 --- /dev/null +++ b/src/views/hooks/useWhatsNew.tsx @@ -0,0 +1,46 @@ +import { Button } from '@views/components/common/Button'; +import Text from '@views/components/common/Text/Text'; +import WhatsNewPopupContent from '@views/components/common/WhatsNewPopup'; +import { useDialog } from '@views/contexts/DialogContext'; +import React from 'react'; + +import { LogoIcon } from '../components/common/LogoIcon'; +import useChangelog from './useChangelog'; + +/** + * Custom hook that provides a function to display a what's new dialog. + * + * @returns A function that, when called, shows a dialog with the changelog. + */ +export default function useWhatsNewPopUp(): () => void { + const showDialog = useDialog(); + const showChangeLog = useChangelog(); + const { version } = chrome.runtime.getManifest(); + + const showPopUp = () => { + showDialog(close => ({ + className: 'w-[830px] flex flex-col items-center gap-spacing-7 p-spacing-8', + title: ( +
+ + + What's New in UT Registration Plus + +
+ ), + description: , + buttons: ( +
+ + +
+ ), + })); + }; + + return showPopUp; +}