From b671df24bbca1a02cad9aa3b9384414268ab8c1e Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Sat, 24 Feb 2024 13:29:02 -0800 Subject: [PATCH 1/4] Add admin pickup event dashboard --- .../admin/store/PickupEventCard/index.tsx | 44 +++++++++++++ .../store/PickupEventCard/style.module.scss | 33 ++++++++++ .../PickupEventCard/style.module.scss.d.ts | 14 ++++ src/lib/api/StoreAPI.ts | 30 +++++++++ src/lib/config.ts | 2 +- src/lib/services/PermissionService.ts | 6 ++ src/pages/admin/index.tsx | 2 +- src/pages/admin/store/pickup/index.tsx | 65 +++++++++++++++++++ .../pages/StorePickupEventPage.module.scss | 36 ++++++++++ .../StorePickupEventPage.module.scss.d.ts | 14 ++++ 10 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 src/components/admin/store/PickupEventCard/index.tsx create mode 100644 src/components/admin/store/PickupEventCard/style.module.scss create mode 100644 src/components/admin/store/PickupEventCard/style.module.scss.d.ts create mode 100644 src/pages/admin/store/pickup/index.tsx create mode 100644 src/styles/pages/StorePickupEventPage.module.scss create mode 100644 src/styles/pages/StorePickupEventPage.module.scss.d.ts diff --git a/src/components/admin/store/PickupEventCard/index.tsx b/src/components/admin/store/PickupEventCard/index.tsx new file mode 100644 index 00000000..f864305e --- /dev/null +++ b/src/components/admin/store/PickupEventCard/index.tsx @@ -0,0 +1,44 @@ +import { Typography } from '@/components/common'; +import { PublicOrderPickupEvent } from '@/lib/types/apiResponses'; +import { OrderPickupEventStatus } from '@/lib/types/enums'; +import { formatEventDate } from '@/lib/utils'; +import styles from './style.module.scss'; + +interface PickupEventCardProps { + pickupEvent: PublicOrderPickupEvent; +} + +const getStatusStyling = (status: OrderPickupEventStatus): string => { + if (status === OrderPickupEventStatus.ACTIVE) { + return styles.active; + } + if (status === OrderPickupEventStatus.COMPLETED) { + return styles.completed; + } + return styles.cancelled; +}; + +const PickupEventCard = ({ pickupEvent }: PickupEventCardProps) => { + const { title, start, end, orders, status } = pickupEvent; + + const statusStyling = getStatusStyling(status); + + return ( +
+
+ + {status} + + {`${orders?.length || 0} orders`} +
+ + {title} + + + {formatEventDate(start, end, true)} + +
+ ); +}; + +export default PickupEventCard; diff --git a/src/components/admin/store/PickupEventCard/style.module.scss b/src/components/admin/store/PickupEventCard/style.module.scss new file mode 100644 index 00000000..c60a3c4a --- /dev/null +++ b/src/components/admin/store/PickupEventCard/style.module.scss @@ -0,0 +1,33 @@ +.card { + background-color: var(--theme-surface-1); + border-radius: 1rem; + box-shadow: 0 0 4px var(--theme-accent-line-1-transparent); + padding: 1rem; + + .title { + transition: 0.3s color; + } + + &:hover > .title { + color: var(--theme-primary-2); + } + + .header { + display: flex; + flex-direction: row; + gap: 1rem; + justify-content: space-between; + } +} + +.active { + color: var(--theme-primary-2); +} + +.completed { + color: var(--theme-success-1); +} + +.cancelled { + color: var(--theme-danger-1); +} diff --git a/src/components/admin/store/PickupEventCard/style.module.scss.d.ts b/src/components/admin/store/PickupEventCard/style.module.scss.d.ts new file mode 100644 index 00000000..b779631c --- /dev/null +++ b/src/components/admin/store/PickupEventCard/style.module.scss.d.ts @@ -0,0 +1,14 @@ +export type Styles = { + active: string; + cancelled: string; + card: string; + completed: string; + header: string; + title: string; +}; + +export type ClassNames = keyof Styles; + +declare const styles: Styles; + +export default styles; diff --git a/src/lib/api/StoreAPI.ts b/src/lib/api/StoreAPI.ts index 1794efe1..453f7e19 100644 --- a/src/lib/api/StoreAPI.ts +++ b/src/lib/api/StoreAPI.ts @@ -25,12 +25,14 @@ import type { GetOneMerchCollectionResponse, GetOneMerchItemResponse, GetOneMerchOrderResponse, + GetOrderPickupEventsResponse, PublicMerchCollection, PublicMerchItem, PublicMerchItemOption, PublicMerchItemPhoto, PublicMerchItemWithPurchaseLimits, PublicOrder, + PublicOrderPickupEvent, PublicOrderWithItems, } from '@/lib/types/apiResponses'; import axios from 'axios'; @@ -316,3 +318,31 @@ export const getCollection = async ( return response.data.collection; }; + +export const getFutureOrderPickupEvents = async ( + token: string +): Promise => { + const requestUrl = `${config.api.baseUrl}${config.api.endpoints.store.pickup.future}`; + + const response = await axios.get(requestUrl, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return response.data.pickupEvents; +}; + +export const getPastOrderPickupEvents = async ( + token: string +): Promise => { + const requestUrl = `${config.api.baseUrl}${config.api.endpoints.store.pickup.past}`; + + const response = await axios.get(requestUrl, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return response.data.pickupEvents; +}; diff --git a/src/lib/config.ts b/src/lib/config.ts index 47eb50be..cc7aa400 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -103,7 +103,7 @@ const config = { viewResumes: '/admin/resumes', store: { items: '/admin/store/items', - pickupEvents: '/admin/store/pickupEvents', + pickup: '/admin/store/pickup', homeRoute: '/admin/store', }, events: { diff --git a/src/lib/services/PermissionService.ts b/src/lib/services/PermissionService.ts index 42d74d70..f9fcaaa8 100644 --- a/src/lib/services/PermissionService.ts +++ b/src/lib/services/PermissionService.ts @@ -5,6 +5,12 @@ import { UserAccessType } from '@/lib/types/enums'; */ export const canEditMerchItems = [UserAccessType.ADMIN, UserAccessType.MERCH_STORE_MANAGER]; +export const canManagePickupEvents = [ + UserAccessType.ADMIN, + UserAccessType.MERCH_STORE_DISTRIBUTOR, + UserAccessType.MERCH_STORE_MANAGER, +]; + export const canManageEvents = [UserAccessType.ADMIN, UserAccessType.MARKETING]; export const canAwardPoints = [UserAccessType.ADMIN]; diff --git a/src/pages/admin/index.tsx b/src/pages/admin/index.tsx index dce14673..c11b2181 100644 --- a/src/pages/admin/index.tsx +++ b/src/pages/admin/index.tsx @@ -56,7 +56,7 @@ const AdminPage = ({ user: { accessType }, preview }: AdminProps) => { {storeAdminVisible ? 'Manage Store Merchandise' : 'View Merch Store'} - Manage Pickup Events + Manage Pickup Events
User Points diff --git a/src/pages/admin/store/pickup/index.tsx b/src/pages/admin/store/pickup/index.tsx new file mode 100644 index 00000000..62f35bf3 --- /dev/null +++ b/src/pages/admin/store/pickup/index.tsx @@ -0,0 +1,65 @@ +import PickupEventCard from '@/components/admin/store/PickupEventCard'; +import { Typography } from '@/components/common'; +import { config } from '@/lib'; +import { StoreAPI } from '@/lib/api'; +import withAccessType from '@/lib/hoc/withAccessType'; +import { CookieService, PermissionService } from '@/lib/services'; +import type { PublicOrderPickupEvent } from '@/lib/types/apiResponses'; +import { CookieType } from '@/lib/types/enums'; +import styles from '@/styles/pages/StorePickupEventPage.module.scss'; +import { GetServerSideProps } from 'next'; +import { useState } from 'react'; + +interface AdminPickupPageProps { + futurePickupEvents: PublicOrderPickupEvent[]; + pastPickupEvents: PublicOrderPickupEvent[]; +} + +const AdminPickupPage = ({ futurePickupEvents, pastPickupEvents }: AdminPickupPageProps) => { + const [display, setDisplay] = useState<'past' | 'future'>('future'); + const displayPickupEvents = display === 'past' ? pastPickupEvents : futurePickupEvents; + return ( +
+
+ Manage Pickup Events + +
+ + +
+
+
+ {displayPickupEvents.map(pickupEvent => ( + + ))} +
+
+ ); +}; + +export default AdminPickupPage; + +const getServerSidePropsFunc: GetServerSideProps = async ({ req, res }) => { + const token = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res }); + const futurePickupEvents = await StoreAPI.getFutureOrderPickupEvents(token); + const pastPickupEvents = await StoreAPI.getPastOrderPickupEvents(token); + return { props: { futurePickupEvents, pastPickupEvents } }; +}; + +export const getServerSideProps = withAccessType( + getServerSidePropsFunc, + PermissionService.canManagePickupEvents, + { redirectTo: config.homeRoute } +); diff --git a/src/styles/pages/StorePickupEventPage.module.scss b/src/styles/pages/StorePickupEventPage.module.scss new file mode 100644 index 00000000..19c6bc5d --- /dev/null +++ b/src/styles/pages/StorePickupEventPage.module.scss @@ -0,0 +1,36 @@ +.page { + display: flex; + flex-direction: column; + gap: 1.5rem; + + .header { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + justify-content: space-between; + + .displayButtons { + border-radius: 1rem; + box-shadow: 0 0 4px var(--theme-accent-line-1-transparent); + display: flex; + flex-direction: row; + overflow: hidden; + + .displayButton { + padding: 0.5rem 1rem; + transition: 0.3s background-color; + + &.active { + background-color: var(--theme-primary-2); + } + } + } + } + + .cardContainer { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + } +} diff --git a/src/styles/pages/StorePickupEventPage.module.scss.d.ts b/src/styles/pages/StorePickupEventPage.module.scss.d.ts new file mode 100644 index 00000000..c80243d8 --- /dev/null +++ b/src/styles/pages/StorePickupEventPage.module.scss.d.ts @@ -0,0 +1,14 @@ +export type Styles = { + active: string; + cardContainer: string; + displayButton: string; + displayButtons: string; + header: string; + page: string; +}; + +export type ClassNames = keyof Styles; + +declare const styles: Styles; + +export default styles; From 464a2bef3701f7850b717bb3bb26d9d0c81083ae Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Sat, 24 Feb 2024 13:34:25 -0800 Subject: [PATCH 2/4] fix import --- src/components/admin/store/index.tsx | 1 + src/pages/admin/store/pickup/index.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/admin/store/index.tsx b/src/components/admin/store/index.tsx index fbe5e438..47ac7dd0 100644 --- a/src/components/admin/store/index.tsx +++ b/src/components/admin/store/index.tsx @@ -1,2 +1,3 @@ export { default as CollectionDetailsForm } from './DetailsForm/CollectionDetailsForm'; export { default as ItemDetailsForm } from './DetailsForm/ItemDetailsForm'; +export { default as PickupEventCard } from './PickupEventCard'; diff --git a/src/pages/admin/store/pickup/index.tsx b/src/pages/admin/store/pickup/index.tsx index 62f35bf3..d2b83aa2 100644 --- a/src/pages/admin/store/pickup/index.tsx +++ b/src/pages/admin/store/pickup/index.tsx @@ -1,4 +1,4 @@ -import PickupEventCard from '@/components/admin/store/PickupEventCard'; +import { PickupEventCard } from '@/components/admin/store'; import { Typography } from '@/components/common'; import { config } from '@/lib'; import { StoreAPI } from '@/lib/api'; From cc550321cc8233bb9e18bfb38b20d6dd884ed1f7 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Sat, 24 Feb 2024 16:12:28 -0800 Subject: [PATCH 3/4] re-trigger CI, cypress is back From fc0544993c42b86203a070100cd073082489166d Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Mon, 26 Feb 2024 13:58:15 -0800 Subject: [PATCH 4/4] batch API calls with promise.all --- src/pages/admin/store/pickup/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/admin/store/pickup/index.tsx b/src/pages/admin/store/pickup/index.tsx index d2b83aa2..a32d3d99 100644 --- a/src/pages/admin/store/pickup/index.tsx +++ b/src/pages/admin/store/pickup/index.tsx @@ -53,8 +53,12 @@ export default AdminPickupPage; const getServerSidePropsFunc: GetServerSideProps = async ({ req, res }) => { const token = CookieService.getServerCookie(CookieType.ACCESS_TOKEN, { req, res }); - const futurePickupEvents = await StoreAPI.getFutureOrderPickupEvents(token); - const pastPickupEvents = await StoreAPI.getPastOrderPickupEvents(token); + const futurePickupEventsPromise = StoreAPI.getFutureOrderPickupEvents(token); + const pastPickupEventsPromise = StoreAPI.getPastOrderPickupEvents(token); + const [futurePickupEvents, pastPickupEvents] = await Promise.all([ + futurePickupEventsPromise, + pastPickupEventsPromise, + ]); return { props: { futurePickupEvents, pastPickupEvents } }; };