feat: add dining app promo (#598)

* feat: add DiningAppPromo component and integrate it into Calendar

* feat: update WhatsNewPopup with new features and app download link

* fix: remove outdated links

* chore: run lint

* chore: run prettier

* feat: enhance DiningAppPromo with close button and integrate user preference for promo visibility

* chore: run lint

* chore: run check types

* fix: correct promo visibility logic in Calendar component

* feat: centralize app store URLs in appUrls.ts

* chore: run lint

* feat: integrate UT Dining promo image

* chore: run lint

* fix: update logo in WhatsNew popup to use LD icon

* fix: convert URLs to URL objects for consistency

* fix: update LD icon in WhatsNew popup to new version

* fix: update description for Coffee Shops feature to clarify operating times

* fix: rename promo state and storage key to showUTDiningPromo for clarity

---------

Co-authored-by: doprz <52579214+doprz@users.noreply.github.com>
This commit is contained in:
Ethan Lanting
2025-05-28 20:13:45 -05:00
committed by GitHub
parent 454e5e807a
commit be1dccfcb9
11 changed files with 174 additions and 49 deletions

View File

@@ -27,6 +27,7 @@ import { Button } from '../common/Button';
import { LargeLogo } from '../common/LogoIcon';
import Text from '../common/Text/Text';
import CalendarFooter from './CalendarFooter';
import DiningAppPromo from './DiningAppPromo';
/**
* Calendar page component
@@ -41,6 +42,8 @@ export default function Calendar(): ReactNode {
const [showPopup, setShowPopup] = useState<boolean>(course !== null);
const showWhatsNewDialog = useWhatsNewPopUp();
const [showUTDiningPromo, setShowUTDiningPromo] = useState<boolean>(false);
const queryClient = useQueryClient();
const { data: showSidebar, isPending: isSidebarStatePending } = useQuery({
queryKey: ['settings', 'showCalendarSidebar'],
@@ -82,12 +85,19 @@ export default function Calendar(): ReactNode {
if (course) setShowPopup(true);
}, [course]);
useEffect(() => {
// Load the user's preference for the promo
OptionsStore.get('showUTDiningPromo').then(show => {
setShowUTDiningPromo(show);
});
}, []);
if (isSidebarStatePending) return null;
return (
<CalendarContext.Provider value>
<div className='h-full w-full flex flex-col'>
<div className='h-screen flex overflow-auto screenshot:calendar-target'>
<div className='screenshot:calendar-target h-screen flex overflow-auto'>
<div
className={clsx(
'py-spacing-6 relative h-full min-h-screen w-full flex flex-none flex-col justify-between overflow-clip whitespace-nowrap border-r border-ut-offwhite/50 shadow-[2px_0_10px,rgba(214_210_196_/_.1)] motion-safe:duration-300 motion-safe:ease-out-expo motion-safe:transition-[max-width] screenshot:hidden',
@@ -122,8 +132,16 @@ export default function Calendar(): ReactNode {
<CalendarSchedules />
<Divider orientation='horizontal' size='100%' />
<ResourceLinks />
<Divider orientation='horizontal' size='100%' />
{/* <TeamLinks /> */}
<Divider orientation='horizontal' size='100%' />
{showUTDiningPromo && (
<DiningAppPromo
onClose={() => {
setShowUTDiningPromo(false);
OptionsStore.set('showUTDiningPromo', false);
}}
/>
)}
<div className='flex flex-col gap-spacing-3'>
<a
href={CRX_PAGES.REPORT}

View File

@@ -0,0 +1,69 @@
import { AppStoreLogo, ForkKnife, X as CloseIcon } from '@phosphor-icons/react';
import { UT_DINING_APP_STORE_URL, UT_DINING_GOOGLE_PLAY_URL } from '@shared/util/appUrls';
import { Button } from '@views/components/common/Button';
import Text from '@views/components/common/Text/Text';
import React from 'react';
interface DiningAppPromoProps {
onClose: () => void;
}
/**
* Promotional component for the UT Dining app
*/
export default function DiningAppPromo({ onClose }: DiningAppPromoProps) {
return (
<div className='relative min-w-[16.25rem] w-full flex items-center gap-spacing-3 border border-ut-offwhite/50 rounded p-spacing-4'>
<div className='flex items-center justify-center'>
<ForkKnife className='h-6 w-6 text-ut-black' />
</div>
<div className='flex flex-col gap-spacing-1'>
<Text as='p' variant='small' className='whitespace-normal text-ut-black'>
Download our new{' '}
<a
href={UT_DINING_APP_STORE_URL}
target='_blank'
rel='noreferrer'
aria-label='UT Dining app'
className='text-ut-burntorange underline'
>
UT Dining app
</a>{' '}
to explore all dining options on campus!
</Text>
<div className='mt-spacing-2 flex items-center gap-spacing-2'>
<Text variant='mini' className='text-ut-black'>
Available on
</Text>
<a
href={UT_DINING_APP_STORE_URL}
target='_blank'
rel='noreferrer'
aria-label='Download on App Store'
className='text-theme-black transition-colors hover:text-ut-burntorange'
>
<AppStoreLogo className='h-4.5 w-4.5' />
</a>
{/* <a
href={UT_DINING_GOOGLE_PLAY_URL}
target='_blank'
rel='noreferrer'
aria-label='Download on Google Play'
className='text-theme-black hover:text-ut-burntorange transition-colors'
>
<GooglePlayLogo className='h-4.5 w-4.5' />
</a> */}
</div>
</div>
<Button
variant='minimal'
color='theme-black'
onClick={onClose}
className='absolute right-1 top-1 h-5 w-5 p-0'
icon={CloseIcon}
aria-label='Close dining app promo'
title='Close'
/>
</div>
);
}

View File

@@ -13,10 +13,10 @@ interface LinkItem {
}
const links: LinkItem[] = [
{
text: "Spring '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
},
// {
// text: "Spring '25 Course Schedule",
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
// },
{
text: 'Course Schedule Archives',
url: 'https://registrar.utexas.edu/schedules/archive',

View File

@@ -22,10 +22,10 @@ const links: LinkItem[] = [
text: "Summer '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20256/',
},
{
text: "Spring '25 Course Schedule",
url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
},
// {
// text: "Spring '25 Course Schedule",
// url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20252/',
// },
{
text: 'Course Schedule Archives',
url: 'https://registrar.utexas.edu/schedules/archive',

View File

@@ -24,7 +24,7 @@ export default function ScheduleDropdown({ defaultOpen, children }: ScheduleDrop
{({ open }) => (
<>
<DisclosureButton className='w-full flex items-center border-none bg-transparent px-3.5 py-2.5 text-left'>
<div className='flex-1 min-w-0 overflow-hidden'>
<div className='min-w-0 flex-1 overflow-hidden'>
<Text
as='div'
variant='h3'

View File

@@ -1,6 +1,7 @@
import type { IconProps } from '@phosphor-icons/react';
import { CloudX, Copy, Exam, MapPinArea, Palette } from '@phosphor-icons/react';
import { CloudX, Coffee, ForkKnife, MapTrifold, Storefront } from '@phosphor-icons/react';
import { ExtensionStore } from '@shared/storage/ExtensionStore';
import { UT_DINING_PROMO_IMAGE_URL } from '@shared/util/appUrls';
import Text from '@views/components/common/Text/Text';
import useWhatsNewPopUp from '@views/hooks/useWhatsNew';
import React, { useEffect, useState } from 'react';
@@ -12,7 +13,7 @@ import React, { useEffect, useState } from 'react';
*
* It should be incremented every time the "What's New" popup is updated.
*/
const WHATSNEW_POPUP_VERSION = 1;
const WHATSNEW_POPUP_VERSION = 2;
const WHATSNEW_VIDEO_URL = 'https://cdn.longhorns.dev/whats-new-v2.1.2.mp4';
@@ -25,35 +26,28 @@ type Feature = {
const NEW_FEATURES = [
{
id: 'custom-course-colors',
icon: Palette,
title: 'Custom Course Colors',
description: 'Paint your schedule in your favorite color theme',
id: 'dining-halls-info',
icon: ForkKnife,
title: 'Dining Halls Info',
description: 'See daily menus and nutritional deets for J2, JCL, and Kins',
},
{
id: 'quick-copy',
icon: Copy,
title: 'Quick Copy',
description: 'Quickly copy a course unique number to your clipboard',
id: 'coffee-shops',
icon: Coffee,
title: 'Coffee Shops',
description: 'Need a Coffee Fix? Check operating times for your favorite campus cafes.',
},
{
id: 'updated-grades',
icon: Exam,
title: 'Updated Grades',
description: 'Fall 2024 grades are now available in the grade distribution',
id: 'convenience-stores',
icon: Storefront,
title: 'Convenience Stores',
description: 'Find hours for quick snacks and essentials on campus.',
},
{
id: 'ut-map',
icon: MapPinArea,
title: (
<div className='flex flex-row items-center'>
UTRP Map
<span className='mx-2 border border-ut-burntorange rounded px-2 py-0.5 text-xs text-ut-burntorange font-medium'>
BETA
</span>
</div>
),
description: 'Find directions to your classes with our beta map feature in the settings page',
id: 'microwave-map',
icon: MapTrifold,
title: 'Microwave Map',
description: 'Need to heat up your lunch? Find microwaves across campus!',
},
] as const satisfies readonly Feature[];
@@ -97,14 +91,11 @@ export default function WhatsNewPopupContent(): JSX.Element {
</div>
</div>
) : (
<video
className='h-fit w-full flex items-center justify-center border border-ut-offwhite/50 rounded object-cover'
autoPlay
loop
muted
>
<source src={WHATSNEW_VIDEO_URL} type='video/mp4' onError={() => setVideoError(true)} />
</video>
<img
className='h-full w-full border border-ut-offwhite/50 rounded object-cover'
src={UT_DINING_PROMO_IMAGE_URL}
alt='UT Dining Promo'
/>
)}
</div>
</div>

View File

@@ -1,3 +1,4 @@
import { UT_DINING_APP_STORE_URL } from '@shared/util/appUrls';
import { Button } from '@views/components/common/Button';
import Text from '@views/components/common/Text/Text';
import WhatsNewPopupContent from '@views/components/common/WhatsNewPopup';
@@ -7,6 +8,8 @@ import React from 'react';
import { LogoIcon } from '../components/common/LogoIcon';
import useChangelog from './useChangelog';
const LDIconURL = new URL('/src/assets/LD-icon-new.png', import.meta.url).href;
/**
* Custom hook that provides a function to display a what's new dialog.
*
@@ -22,20 +25,26 @@ export default function useWhatsNewPopUp(): () => void {
className: 'w-[830px] flex flex-col items-center gap-spacing-7 p-spacing-8',
title: (
<div className='flex items-center justify-between gap-4'>
<LogoIcon width='48' height='48' />
<img src={LDIconURL} alt='LD Icon' className='h-12 w-12 rounded-lg' />
<Text variant='h1' className='text-theme-black'>
What&apos;s New in UT Registration Plus
Download our new UT Dining app!
</Text>
</div>
),
description: <WhatsNewPopupContent />,
buttons: (
<div className='flex flex-row items-end gap-spacing-4'>
<Button onClick={showChangeLog} variant='minimal' color='ut-black'>
Read Changelog v{version}
<Button
onClick={() => {
window.open(UT_DINING_APP_STORE_URL, '_blank');
}}
variant='minimal'
color='ut-black'
>
Download UT Dining on iOS
</Button>
<Button onClick={close} color='ut-burntorange'>
Get started
Close
</Button>
</div>
),