Skip to content

Commit e24134b

Browse files
authored
Migrate to purchase ops with new ACM store APIs in Core (#269)
1 parent 58a75e2 commit e24134b

File tree

4 files changed

+206
-71
lines changed

4 files changed

+206
-71
lines changed

src/app/(membership)/merch-paid/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Lottie from 'lottie-react';
1212
import axios from 'axios';
1313
import Layout from '../MembershipLayout';
1414
import successAnimation from '../success.json';
15+
import { transformApiResponse } from '../merch-store/transform';
1516

1617
const WrapepdMerchPaid = () => {
1718
return (
@@ -25,12 +26,12 @@ const MerchPaid = () => {
2526
const itemid = useSearchParams().get('id') || '';
2627
const [merchList, setMerchList] = useState<Record<string, any>>({});
2728

28-
const baseUrl = process.env.NEXT_PUBLIC_MERCH_API_BASE_URL;
29+
const coreBaseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
2930

3031
const metaLoader = async () => {
31-
const url = `${baseUrl}/api/v1/merch/details?itemid=${itemid}`;
32+
const url = `${coreBaseUrl}/api/v1/store/products/${itemid}`;
3233
axios.get(url).then((response) => {
33-
setMerchList(response.data);
34+
setMerchList(transformApiResponse({ products: [response.data] })[0]);
3435
});
3536
};
3637

src/app/(membership)/merch-store/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { useState } from 'react';
44
import { Button, Card, CardBody, CardHeader, Link } from '@heroui/react';
55
import axios from 'axios';
66
import Layout from '../MembershipLayout';
7+
import { transformApiResponse } from './transform';
78

89
const MerchStore = () => {
910
const [itemsList, setItemsList] = useState<Array<Record<string, any>>>([]);
10-
const baseUrl = process.env.NEXT_PUBLIC_MERCH_API_BASE_URL;
11+
const baseUrl = process.env.NEXT_PUBLIC_CORE_API_BASE_URL;
1112
const decimalHelper = (num: number) => {
1213
if (Number.isInteger(num)) {
1314
return num;
@@ -16,13 +17,14 @@ const MerchStore = () => {
1617
}
1718
};
1819
const metaLoader = async () => {
19-
const url = `${baseUrl}/api/v1/merch/all_item_details`;
20+
const url = `${baseUrl}/api/v1/store/products`;
2021
axios
2122
.get(url)
2223
.then((response) => {
23-
setItemsList(response.data);
24+
setItemsList(transformApiResponse(response.data));
2425
})
2526
.catch((error) => {
27+
console.error(error)
2628
setItemsList([
2729
{
2830
member_price: '',
@@ -74,7 +76,7 @@ const MerchStore = () => {
7476
<p>
7577
<b>Cost:</b> ${decimalHelper(val['item_price']['paid'])} for{' '}
7678
{val['valid_member_lists'] &&
77-
val['valid_member_lists'].length > 0
79+
val['valid_member_lists'].length > 0
7880
? 'paid ACM@UIUC and eligible partner organization'
7981
: 'paid ACM@UIUC'}{' '}
8082
members, ${decimalHelper(val['item_price']['others'])} for
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
interface ApiVariant {
2+
variantId: string;
3+
name: string;
4+
description?: string;
5+
imageUrl?: string;
6+
memberLists: string[];
7+
inventoryCount?: number | null;
8+
exchangesAllowed: boolean;
9+
memberPriceCents: number;
10+
nonmemberPriceCents: number;
11+
}
12+
13+
interface ApiProduct {
14+
productId: string;
15+
name: string;
16+
description?: string;
17+
imageUrl?: string;
18+
openAt?: number;
19+
closeAt?: number;
20+
limitConfiguration?: {
21+
limitType: "PER_PRODUCT" | "PER_VARIANT";
22+
maxQuantity: number;
23+
};
24+
verifiedIdentityRequired: boolean;
25+
inventoryMode: "PER_PRODUCT" | "PER_VARIANT";
26+
totalInventoryCount?: number | null;
27+
variants: ApiVariant[];
28+
}
29+
30+
interface ApiResponse {
31+
products: ApiProduct[];
32+
}
33+
34+
interface LegacyItem {
35+
member_price: string;
36+
nonmember_price: string;
37+
item_image: string;
38+
sizes: string[];
39+
item_price: { paid: number; others: number };
40+
eventDetails: string;
41+
item_id: string;
42+
total_sold: Record<string, number>;
43+
total_avail: Record<string, number>;
44+
limit_per_person: number;
45+
item_sales_active_utc: number;
46+
item_name: string;
47+
}
48+
49+
function formatPriceRange(prices: number[]): string {
50+
if (prices.length === 0) return "";
51+
52+
const uniquePrices = Array.from(new Set(prices)).sort((a, b) => a - b);
53+
const formatPrice = (cents: number) => `$${(cents / 100).toFixed(2)}`;
54+
55+
if (uniquePrices.length === 1) {
56+
return formatPrice(uniquePrices[0] / 100);
57+
}
58+
59+
return `${formatPrice(uniquePrices[0] / 100)} - ${formatPrice(uniquePrices[uniquePrices.length - 1] / 100)}`;
60+
}
61+
62+
export function transformApiResponse(apiResponse: ApiResponse): LegacyItem[] {
63+
return apiResponse.products.map((product) => {
64+
const memberPrices = product.variants.map((v) => v.memberPriceCents);
65+
const nonmemberPrices = product.variants.map((v) => v.nonmemberPriceCents);
66+
67+
// For item_price, use the minimum prices (or you could use average, etc.)
68+
const minMemberPrice = Math.min(...memberPrices);
69+
const minNonmemberPrice = Math.min(...nonmemberPrices);
70+
71+
// Build total_avail from variant inventory
72+
const total_avail: Record<string, number> = {};
73+
if (product.inventoryMode === "PER_VARIANT") {
74+
product.variants.forEach((variant) => {
75+
if (variant.inventoryCount != null) {
76+
total_avail[variant.variantId] = variant.inventoryCount;
77+
}
78+
});
79+
} else if (product.totalInventoryCount != null) {
80+
// For PER_PRODUCT mode, could assign total to a generic key or distribute
81+
total_avail["total"] = product.totalInventoryCount;
82+
}
83+
84+
return {
85+
item_id: product.productId,
86+
item_name: product.name,
87+
item_image: product.imageUrl ?? "",
88+
description: product.description,
89+
member_price: formatPriceRange(memberPrices),
90+
nonmember_price: formatPriceRange(nonmemberPrices),
91+
item_price: {
92+
paid: minMemberPrice / 100,
93+
others: minNonmemberPrice / 100,
94+
},
95+
sizes: product.variants.map((v) => v.name),
96+
variants: product.variants.map(v => ({
97+
id: v.variantId,
98+
name: v.name
99+
})),
100+
eventDetails: product.description ?? "",
101+
total_sold: {}, // API doesn't provide this; initialize empty
102+
total_avail,
103+
limit_per_person: product.limitConfiguration?.maxQuantity ?? -1,
104+
item_sales_active_utc: product.openAt ?? -1,
105+
};
106+
});
107+
}

0 commit comments

Comments
 (0)