Skip to content

Commit 754563b

Browse files
authored
Update tshirt-availability.tsx
1 parent db0181e commit 754563b

1 file changed

Lines changed: 131 additions & 82 deletions

File tree

‎components/tshirt-availability.tsx‎

Lines changed: 131 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,120 @@
1-
"use client"
2-
import { useState, useEffect, useRef } from "react"
3-
import { RefreshCw, AlertTriangle, Loader2, Lock } from "lucide-react"
4-
import type { ApiResponse, SizeAvailability } from "@/types"
1+
"use client";
2+
import { useState, useEffect, useRef } from "react";
3+
import { RefreshCw, AlertTriangle, Loader2, Lock } from "lucide-react";
4+
import type { ApiResponse, SizeAvailability } from "@/types";
55

66
// API endpoint URL - use our proxy API route
7-
const API_URL = "/api/tshirt-data"
7+
const API_URL =
8+
"https://script.google.com/macros/s/AKfycbz8j2mEAYbOlsLHEIFwunY1ygxR0xqJeL0EPHdUYUpG_RcJYtXKe4_MyaOyBhB5LJFE/exec";
89

910
// Constants for determining stock status
10-
const LOW_STOCK_THRESHOLD = 5
11-
const FETCH_INTERVAL = 25000 // 25 Seconds
11+
const LOW_STOCK_THRESHOLD = 5;
12+
const FETCH_INTERVAL = 25000; // 25 Seconds
1213

1314
// Set this to true to enable fetching by default, false to disable
14-
const ENABLE_FETCHING = true
15+
const ENABLE_FETCHING = true;
1516

1617
export default function TShirtAvailability() {
17-
const [sizes, setSizes] = useState<SizeAvailability[]>([])
18-
const [lastUpdated, setLastUpdated] = useState<string>("Never")
19-
const [isLoading, setIsLoading] = useState<boolean>(true)
20-
const [error, setError] = useState<string | null>(null)
21-
const [fetchCount, setFetchCount] = useState(0) // Add this to force re-renders
22-
const [isMockData, setIsMockData] = useState(false)
23-
const intervalRef = useRef<NodeJS.Timeout | null>(null)
18+
const [sizes, setSizes] = useState<SizeAvailability[]>([]);
19+
const [lastUpdated, setLastUpdated] = useState<string>("Never");
20+
const [isLoading, setIsLoading] = useState<boolean>(true);
21+
const [error, setError] = useState<string | null>(null);
22+
const [fetchCount, setFetchCount] = useState(0); // Add this to force re-renders
23+
const [isMockData, setIsMockData] = useState(false);
24+
const intervalRef = useRef<NodeJS.Timeout | null>(null);
2425

2526
// Function to transform API response to our format
2627
const transformApiData = (data: ApiResponse): SizeAvailability[] => {
2728
return Object.entries(data)
2829
.map(([size, sizeData]) => {
2930
// Determine status based on availability
30-
let status: "available" | "low" | "out" = "out"
31+
let status: "available" | "low" | "out" = "out";
3132
if (sizeData.availability > 0) {
32-
status = sizeData.availability <= LOW_STOCK_THRESHOLD ? "low" : "available"
33+
status =
34+
sizeData.availability <= LOW_STOCK_THRESHOLD ? "low" : "available";
3335
}
3436

3537
return {
3638
size,
3739
total: sizeData.stockReceived,
3840
available: sizeData.availability,
3941
status,
40-
}
42+
};
4143
})
4244
.sort((a, b) => {
4345
// Custom sort order for sizes
44-
const sizeOrder = ["3XS", "2XS", "XS", "S", "M", "L", "XL", "2XL", "3XL"]
45-
return sizeOrder.indexOf(a.size) - sizeOrder.indexOf(b.size)
46-
})
47-
}
46+
const sizeOrder = [
47+
"3XS",
48+
"2XS",
49+
"XS",
50+
"S",
51+
"M",
52+
"L",
53+
"XL",
54+
"2XL",
55+
"3XL",
56+
];
57+
return sizeOrder.indexOf(a.size) - sizeOrder.indexOf(b.size);
58+
});
59+
};
4860

4961
// Function to fetch data from API
5062
const fetchData = async () => {
5163
try {
52-
setIsLoading(true)
53-
setError(null)
54-
setIsMockData(false)
64+
setIsLoading(true);
65+
setError(null);
66+
setIsMockData(false);
5567

56-
console.log("Fetching data from:", API_URL) // Debug log
68+
console.log("Fetching data from:", API_URL); // Debug log
5769

58-
// Fetch from our server-side API route
70+
// Fetch directly from the external API for real-time data
5971
const response = await fetch(API_URL, {
6072
method: "GET",
6173
headers: {
6274
"Content-Type": "application/json",
6375
},
6476
cache: "no-store",
65-
})
77+
});
6678

67-
console.log("Response status:", response.status) // Debug log
79+
console.log("Response status:", response.status); // Debug log
6880

6981
if (!response.ok) {
70-
throw new Error(`HTTP error! Status: ${response.status}`)
82+
throw new Error(`HTTP error! Status: ${response.status}`);
7183
}
7284

7385
// Check if we're getting mock data
74-
const isMock = response.headers.get("X-Mock-Data") === "true"
75-
setIsMockData(isMock)
86+
const isMock = response.headers.get("X-Mock-Data") === "true";
87+
setIsMockData(isMock);
7688

7789
if (isMock) {
78-
const errorMessage = response.headers.get("X-Error-Message")
79-
console.warn("Using mock data:", errorMessage)
80-
setError(`Using demo data: ${errorMessage || "API unavailable"}`)
90+
const errorMessage = response.headers.get("X-Error-Message");
91+
console.warn("Using mock data:", errorMessage);
92+
setError(`Using demo data: ${errorMessage || "API unavailable"}`);
8193
}
8294

83-
const data = await response.json()
84-
console.log("Received data:", data) // Debug log
95+
const data = await response.json();
96+
console.log("Received data:", data); // Debug log
8597

8698
// Check if data is empty or has the expected structure
8799
if (!data || Object.keys(data).length === 0) {
88-
throw new Error("No data available")
100+
throw new Error("No data available");
89101
}
90102

91103
// Check if we got an authentication error
92104
if (data.error && data.error.includes("authentication")) {
93-
throw new Error("Authentication required. Please sign in to access the data.")
105+
throw new Error(
106+
"Authentication required. Please sign in to access the data."
107+
);
94108
}
95109

96-
const transformedData = transformApiData(data)
97-
setSizes(transformedData)
98-
setLastUpdated(new Date().toLocaleString() + (isMock ? " (demo data)" : ""))
99-
setFetchCount((prev) => prev + 1) // Increment to force re-render
110+
const transformedData = transformApiData(data);
111+
setSizes(transformedData);
112+
setLastUpdated(
113+
new Date().toLocaleString() + (isMock ? " (demo data)" : "")
114+
);
115+
setFetchCount((prev) => prev + 1); // Increment to force re-render
100116
} catch (err: any) {
101-
console.error("Error fetching data:", err)
117+
console.error("Error fetching data:", err);
102118

103119
// Use mock data for demonstration
104120
const mockData: ApiResponse = {
@@ -111,33 +127,36 @@ export default function TShirtAvailability() {
111127
XL: { distributed: 40, stockReceived: 50, availability: 10 },
112128
"2XL": { distributed: 20, stockReceived: 30, availability: 10 },
113129
"3XL": { distributed: 5, stockReceived: 10, availability: 5 },
114-
}
130+
};
115131

116-
setSizes(transformApiData(mockData))
117-
setLastUpdated(new Date().toLocaleString() + " (demo data)")
118-
setIsMockData(true)
132+
setSizes(transformApiData(mockData));
133+
setLastUpdated(new Date().toLocaleString() + " (demo data)");
134+
setIsMockData(true);
119135

120136
// Set error message
121137
if (err.message.includes("authentication")) {
122-
setError("Authentication required. Please sign in to access the data.")
138+
setError("Authentication required. Please sign in to access the data.");
123139
} else if (err.message.includes("No data available")) {
124-
setError("No T-shirt data available. Please check back later.")
125-
} else if (err.name === "TypeError" && err.message.includes("Failed to fetch")) {
126-
setError("Network error. Please check your connection and try again.")
140+
setError("No T-shirt data available. Please check back later.");
141+
} else if (
142+
err.name === "TypeError" &&
143+
err.message.includes("Failed to fetch")
144+
) {
145+
setError("Network error. Please check your connection and try again.");
127146
} else {
128-
setError(`Using demo data: ${err.message}`)
147+
setError(`Using demo data: ${err.message}`);
129148
}
130149
} finally {
131-
setIsLoading(false)
150+
setIsLoading(false);
132151
}
133-
}
152+
};
134153

135154
// Set up auto-fetch interval
136155
useEffect(() => {
137156
// Only set up the interval if ENABLE_FETCHING is true
138157
if (ENABLE_FETCHING) {
139-
fetchData() // Fetch immediately when component mounts
140-
intervalRef.current = setInterval(fetchData, FETCH_INTERVAL)
158+
fetchData(); // Fetch immediately when component mounts
159+
intervalRef.current = setInterval(fetchData, FETCH_INTERVAL);
141160
} else {
142161
// If fetching is disabled, use mock data
143162
const mockData: ApiResponse = {
@@ -150,24 +169,27 @@ export default function TShirtAvailability() {
150169
XL: { distributed: 40, stockReceived: 50, availability: 10 },
151170
"2XL": { distributed: 20, stockReceived: 30, availability: 10 },
152171
"3XL": { distributed: 5, stockReceived: 10, availability: 5 },
153-
}
172+
};
154173

155-
setSizes(transformApiData(mockData))
156-
setLastUpdated("Using demo data (fetching disabled)")
157-
setIsLoading(false)
158-
setIsMockData(true)
174+
setSizes(transformApiData(mockData));
175+
setLastUpdated("Using demo data (fetching disabled)");
176+
setIsLoading(false);
177+
setIsMockData(true);
159178
}
160179

161180
// Clean up the interval when the component unmounts
162181
return () => {
163182
if (intervalRef.current) {
164-
clearInterval(intervalRef.current)
183+
clearInterval(intervalRef.current);
165184
}
166-
}
167-
}, [])
185+
};
186+
}, []);
168187

169188
return (
170-
<section className="py-12 bg-gradient-to-b from-black to-black/95" id="availability">
189+
<section
190+
className="py-12 bg-gradient-to-b from-black to-black/95"
191+
id="availability"
192+
>
171193
<div className="container mx-auto px-4">
172194
<div className="max-w-4xl mx-auto">
173195
<div className="mb-10 text-center">
@@ -188,7 +210,9 @@ export default function TShirtAvailability() {
188210
) : (
189211
<RefreshCw className="w-4 h-4 text-white/40 mr-2" />
190212
)}
191-
<span className="text-sm text-white/60">Last updated: {lastUpdated}</span>
213+
<span className="text-sm text-white/60">
214+
Last updated: {lastUpdated}
215+
</span>
192216
</div>
193217

194218
{/* Error message */}
@@ -201,7 +225,9 @@ export default function TShirtAvailability() {
201225
<AlertTriangle className="w-5 h-5 text-yellow-500" />
202226
)}
203227
<span className="font-medium text-yellow-500">
204-
{error.includes("Authentication") ? "Authentication Required" : "Using Demo Data"}
228+
{error.includes("Authentication")
229+
? "Authentication Required"
230+
: "Using Demo Data"}
205231
</span>
206232
</div>
207233
<p className="text-white/70">{error}</p>
@@ -220,7 +246,10 @@ export default function TShirtAvailability() {
220246
{sizes.length > 0 && (
221247
<div className="grid grid-cols-2 md:grid-cols-3 gap-6 mt-6">
222248
{sizes.map((sizeItem) => (
223-
<SizeCard key={`${sizeItem.size}-${fetchCount}`} sizeItem={sizeItem} />
249+
<SizeCard
250+
key={`${sizeItem.size}-${fetchCount}`}
251+
sizeItem={sizeItem}
252+
/>
224253
))}
225254
</div>
226255
)}
@@ -231,7 +260,8 @@ export default function TShirtAvailability() {
231260
<AlertTriangle className="w-10 h-10 text-yellow-500 mx-auto mb-4" />
232261
<h3 className="text-xl font-medium mb-2">No Data Available</h3>
233262
<p className="text-white/60">
234-
We couldn't find any T-shirt availability data. Please check back later.
263+
We couldn't find any T-shirt availability data. Please check
264+
back later.
235265
</p>
236266
</div>
237267
)}
@@ -267,36 +297,53 @@ export default function TShirtAvailability() {
267297
</div>
268298
</div>
269299
</section>
270-
)
300+
);
271301
}
272302

273303
type SizeCardProps = {
274-
sizeItem: SizeAvailability
275-
}
304+
sizeItem: SizeAvailability;
305+
};
276306

277307
function SizeCard({ sizeItem }: SizeCardProps) {
278-
const percentage = Math.round((sizeItem.available / sizeItem.total) * 100) || 0
308+
const percentage =
309+
Math.round((sizeItem.available / sizeItem.total) * 100) || 0;
279310

280311
// Determine status color
281312
const statusColor =
282-
sizeItem.status === "available" ? "bg-green-500" : sizeItem.status === "low" ? "bg-yellow-500" : "bg-red-500"
313+
sizeItem.status === "available"
314+
? "bg-green-500"
315+
: sizeItem.status === "low"
316+
? "bg-yellow-500"
317+
: "bg-red-500";
283318

284319
const textColor =
285-
sizeItem.status === "available" ? "text-green-500" : sizeItem.status === "low" ? "text-yellow-500" : "text-red-500"
320+
sizeItem.status === "available"
321+
? "text-green-500"
322+
: sizeItem.status === "low"
323+
? "text-yellow-500"
324+
: "text-red-500";
286325

287326
const borderColor =
288327
sizeItem.status === "available"
289328
? "border-green-500/30"
290329
: sizeItem.status === "low"
291-
? "border-yellow-500/30"
292-
: "border-red-500/30"
330+
? "border-yellow-500/30"
331+
: "border-red-500/30";
293332

294333
const bgColor =
295-
sizeItem.status === "available" ? "bg-green-500/5" : sizeItem.status === "low" ? "bg-yellow-500/5" : "bg-red-500/5"
334+
sizeItem.status === "available"
335+
? "bg-green-500/5"
336+
: sizeItem.status === "low"
337+
? "bg-yellow-500/5"
338+
: "bg-red-500/5";
296339

297340
// Determine status text
298341
const statusText =
299-
sizeItem.status === "available" ? "In Stock" : sizeItem.status === "low" ? "Low Stock" : "Out of Stock"
342+
sizeItem.status === "available"
343+
? "In Stock"
344+
: sizeItem.status === "low"
345+
? "Low Stock"
346+
: "Out of Stock";
300347

301348
return (
302349
<div className={`p-5 rounded-lg border ${borderColor} ${bgColor}`}>
@@ -306,7 +353,9 @@ function SizeCard({ sizeItem }: SizeCardProps) {
306353
</div>
307354

308355
{/* Status text */}
309-
<div className={`text-sm font-medium mb-3 ${textColor}`}>{statusText}</div>
356+
<div className={`text-sm font-medium mb-3 ${textColor}`}>
357+
{statusText}
358+
</div>
310359

311360
{/* Progress bar */}
312361
<div className="h-3 bg-white/10 rounded-full overflow-hidden">
@@ -318,5 +367,5 @@ function SizeCard({ sizeItem }: SizeCardProps) {
318367
></div>
319368
</div>
320369
</div>
321-
)
370+
);
322371
}

0 commit comments

Comments
 (0)