mirror of
https://github.com/UiO-Gaming/mustaboard.git
synced 2026-11-05 13:56:52 +01:00
v1 release
- various formatting changes - info card displays vipps information - sanity integration for contact information, can append events later - time is displayed in human-readable format to avoid confusion (e.g., ISO vs US vs EU)
This commit is contained in:
1 parent
96ef605ab4
commit
26993e3520
13 files changed
+240
-66
No files matched your search
@@ -34,3 +34,6 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# config
|
||||
config.tsx
|
||||
@@ -21,14 +21,26 @@ interface Place {
|
||||
timeDiffMs?: number;
|
||||
}
|
||||
|
||||
interface EventCardProps {
|
||||
recurringTimeAhead: string;
|
||||
specialTimeAhead: string;
|
||||
placeTimeAhead: string;
|
||||
}
|
||||
|
||||
const recurringEvents: RecurringEvent[] = eventItems.recurring_events;
|
||||
const specialEvents: SpecialEvent[] = eventItems.special_events;
|
||||
const places: Place[] = eventItems.places.map((place: any) => ({
|
||||
name: place.name,
|
||||
closingHours: new Map(place.closing_times.map((time: any) => [time.day, time.time])),
|
||||
closingHours: new Map(
|
||||
place.closing_times.map((time: any) => [time.day, time.time])
|
||||
),
|
||||
}));
|
||||
|
||||
const EventCard: React.FC = () => {
|
||||
const EventCard: React.FC<EventCardProps> = ({
|
||||
recurringTimeAhead,
|
||||
specialTimeAhead,
|
||||
placeTimeAhead,
|
||||
}) => {
|
||||
const [upcomingEvents, setUpcomingEvents] = useState<
|
||||
(RecurringEvent | SpecialEvent | Place)[]
|
||||
>([]);
|
||||
@@ -36,21 +48,24 @@ const EventCard: React.FC = () => {
|
||||
useEffect(() => {
|
||||
const updateEvents = () => {
|
||||
const now = new Date();
|
||||
// Handle recurring events
|
||||
// handle recurring events
|
||||
const eventsWithCountdown = recurringEvents.map((event) => {
|
||||
const eventTime = getNextRecurringEvent(event.day, event.time || "00:00");
|
||||
const eventTime = getNextRecurringEvent(
|
||||
event.day,
|
||||
event.time || "00:00"
|
||||
);
|
||||
const timeDiffMs = eventTime.getTime() - now.getTime();
|
||||
return { ...event, timeDiffMs };
|
||||
});
|
||||
|
||||
// Handle places' closing times
|
||||
// handle places' closing times
|
||||
const placesWithCountdown = places.map((place) => {
|
||||
const closingTime = getNextClosingTime(place.closingHours);
|
||||
const timeDiffMs = closingTime.getTime() - now.getTime();
|
||||
return { ...place, timeDiffMs };
|
||||
});
|
||||
|
||||
// Handle special events
|
||||
// handle special events
|
||||
const specialEventsWithCountdown = specialEvents.map((event) => {
|
||||
const eventTime = new Date(`${event.date}T${event.time}`);
|
||||
const timeDiffMs = eventTime.getTime() - now.getTime();
|
||||
@@ -59,17 +74,23 @@ const EventCard: React.FC = () => {
|
||||
|
||||
const filteredEvents = [
|
||||
...eventsWithCountdown.filter(
|
||||
(event) => event.timeDiffMs! > 0 && event.timeDiffMs! <= 48 * 60 * 60 * 1000
|
||||
),
|
||||
...placesWithCountdown.filter(
|
||||
(place) => place.timeDiffMs! > 0 && place.timeDiffMs! <= 4 * 60 * 60 * 1000
|
||||
(event) =>
|
||||
event.timeDiffMs! > 0 &&
|
||||
event.timeDiffMs! <= parseInt(recurringTimeAhead) * 60 * 60 * 1000 // 4 days
|
||||
),
|
||||
...specialEventsWithCountdown.filter(
|
||||
(event) => event.timeDiffMs! > 0 && event.timeDiffMs! <= 48 * 60 * 60 * 1000
|
||||
(event) =>
|
||||
event.timeDiffMs! > 0 &&
|
||||
event.timeDiffMs! <= parseInt(specialTimeAhead) * 60 * 60 * 1000 // 4 days
|
||||
),
|
||||
...placesWithCountdown.filter(
|
||||
(place) =>
|
||||
place.timeDiffMs! > 0 &&
|
||||
place.timeDiffMs! <= parseInt(placeTimeAhead) * 60 * 60 * 1000 // 4 hours
|
||||
),
|
||||
];
|
||||
|
||||
// Sort events by time difference
|
||||
// sort events by time difference
|
||||
filteredEvents.sort((a, b) => a.timeDiffMs! - b.timeDiffMs!);
|
||||
|
||||
setUpcomingEvents(filteredEvents);
|
||||
@@ -85,13 +106,18 @@ const EventCard: React.FC = () => {
|
||||
<div className="flex flex-col bg-foreground rounded-lg p-3">
|
||||
<div className="flex flex-col gap-3">
|
||||
{upcomingEvents.map((event) => (
|
||||
<div className="flex flex-1 flex-row bg-primary rounded-lg p-3 justify-between space" key={event.name}>
|
||||
<div
|
||||
className="flex flex-1 flex-row bg-primary rounded-lg p-3 justify-between space"
|
||||
key={event.name}
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<p className="event-type">{'closingHours' in event ? "Closing" : "Event"}</p>
|
||||
<p>{event.name}</p>
|
||||
<p className="parabold">
|
||||
{"closingHours" in event ? "Closing" : "Event"}
|
||||
</p>
|
||||
<p className="para">{event.name}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<p>{getTimeDifference(event.timeDiffMs ?? 0)}</p>
|
||||
<p className="para">{getTimeDifference(event.timeDiffMs ?? 0)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -103,11 +129,11 @@ const EventCard: React.FC = () => {
|
||||
const getNextRecurringEvent = (day: string, time: string): Date => {
|
||||
/**
|
||||
* Returns the next occurrence of a recurring event.
|
||||
*
|
||||
*
|
||||
* @param {string} day - The day of the week the event occurs on.
|
||||
* @param {string} time - The time of day the event occurs at.
|
||||
* @returns {Date} The next occurrence of the event.
|
||||
*
|
||||
*
|
||||
*/
|
||||
const daysOfWeek: { [key: string]: number } = {
|
||||
Sunday: 0,
|
||||
@@ -145,18 +171,18 @@ const getNextRecurringEvent = (day: string, time: string): Date => {
|
||||
const getNextClosingTime = (closingHours: Map<string, string>): Date => {
|
||||
/**
|
||||
* Returns the next closing time for a place.
|
||||
*
|
||||
*
|
||||
* @param {Map<string, string>} closingHours - A map of closing times for each day of the week.
|
||||
*
|
||||
*
|
||||
* @returns {Date} The next closing time for the place.
|
||||
*
|
||||
*
|
||||
*/
|
||||
const now = new Date();
|
||||
const currentDay = now.toLocaleDateString("en-US", { weekday: "long" });
|
||||
|
||||
const closingTime = closingHours.get(currentDay);
|
||||
if (!closingTime) {
|
||||
return now; // If no closing time for today, return now (or handle differently)
|
||||
return now;
|
||||
}
|
||||
|
||||
const [closingHour, closingMinute] = closingTime.split(":").map(Number);
|
||||
@@ -164,12 +190,15 @@ const getNextClosingTime = (closingHours: Map<string, string>): Date => {
|
||||
closingDate.setHours(closingHour, closingMinute, 0, 0);
|
||||
|
||||
if (now > closingDate) {
|
||||
// If already past today's closing time, get the next day's closing time
|
||||
const nextDay = new Date(now.getTime() + 24 * 60 * 60 * 1000); // add 1 day
|
||||
const nextDayName = nextDay.toLocaleDateString("en-US", { weekday: "long" });
|
||||
const nextDayName = nextDay.toLocaleDateString("en-US", {
|
||||
weekday: "long",
|
||||
});
|
||||
const nextClosingTime = closingHours.get(nextDayName);
|
||||
if (nextClosingTime) {
|
||||
const [nextClosingHour, nextClosingMinute] = nextClosingTime.split(":").map(Number);
|
||||
const [nextClosingHour, nextClosingMinute] = nextClosingTime
|
||||
.split(":")
|
||||
.map(Number);
|
||||
closingDate.setDate(closingDate.getDate() + 1); // Move to the next day
|
||||
closingDate.setHours(nextClosingHour, nextClosingMinute, 0, 0);
|
||||
}
|
||||
@@ -181,11 +210,11 @@ const getNextClosingTime = (closingHours: Map<string, string>): Date => {
|
||||
const getTimeDifference = (timeDiff: number): string => {
|
||||
/**
|
||||
* Returns a human-readable string representing the time difference.
|
||||
*
|
||||
*
|
||||
* @param {number} timeDiff - The time difference in milliseconds.
|
||||
*
|
||||
*
|
||||
* @returns {string} A human-readable string representing the time difference.
|
||||
*
|
||||
*
|
||||
*/
|
||||
const msInHour = 1000 * 60 * 60;
|
||||
const msInDay = msInHour * 24;
|
||||
|
||||
@@ -1 +1,38 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import vipps from "../../public/images/vipps.png";
|
||||
import Image from 'next/image';
|
||||
import SanityPeople from "../people";
|
||||
import SanityEvents from "../events";
|
||||
|
||||
const InfoCard: React.FC = () => {
|
||||
interface Person {
|
||||
name: string;
|
||||
telephone: string;
|
||||
}
|
||||
|
||||
interface Event {
|
||||
name: string;
|
||||
date: string;
|
||||
time: string;
|
||||
timeDiffMs?: number;
|
||||
}
|
||||
|
||||
const data: Person[] = SanityPeople();
|
||||
const person = data.find((person: Person) => person.telephone) || { name: "", telephone: "" };
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-primary rounded-lg p-3">
|
||||
<div className="flex flex-row gap-3 place-content-between items-center">
|
||||
<div className="w-1/4">
|
||||
<Image src={vipps} alt="Vipps"/>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-end">
|
||||
<p className="parabold">{person.name}</p>
|
||||
<p className="para">{person.telephone}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoCard;
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import menuItems from "../../public/menuItems.json";
|
||||
import MenuSection from "./MenuSection";
|
||||
import InfoCard from "./InfoCard";
|
||||
|
||||
function MenuCard() {
|
||||
return (
|
||||
@@ -16,9 +17,9 @@ function MenuCard() {
|
||||
<MenuSection title="Soda and Energy Drinks" items={menuItems.Soda}></MenuSection>
|
||||
|
||||
{/*bottom box*/}
|
||||
<div className="flex-1 bg-primary rounded-lg p-3">
|
||||
<h1 className="text-headers">Info</h1>
|
||||
</div>
|
||||
<InfoCard>
|
||||
|
||||
</InfoCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ const MenuSection: React.FC<MenuSectionProps> = ({ title, items }) => {
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{items.map((item) => (
|
||||
<div key={item.name} className="flex flex-row justify-end">
|
||||
<p className="text-headers text-right">kr {item.price}</p>
|
||||
<p className="text-p text-right">kr {item.price}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ const TemperatureCard: React.FC<TemperatureCardProps> = ({
|
||||
fiveHourForecast,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col bg-foreground rounded-lg p-3 gap-3 h-full">
|
||||
<div className="flex flex-col bg-foreground rounded-lg p-3 gap-3">
|
||||
{/* two vertical containers */}
|
||||
<div className="flex flex-row gap-3">
|
||||
{/* left container */}
|
||||
@@ -23,10 +23,10 @@ const TemperatureCard: React.FC<TemperatureCardProps> = ({
|
||||
</div>
|
||||
<div className="flex flex-col bg-primary rounded-lg p-3">
|
||||
<div className="flex flex-row place-content-between">
|
||||
<div className="text-headers text-p forecasts">
|
||||
<div className="text-headers text-p para">
|
||||
{precipitation + " mm"}
|
||||
</div>
|
||||
<div className="text-headers text-p forecasts">
|
||||
<div className="text-headers text-p para">
|
||||
{wind.speed + "(" + wind.gust + ") m/s"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,8 +41,8 @@ const TemperatureCard: React.FC<TemperatureCardProps> = ({
|
||||
className="flex flex-1 flex-col bg-primary rounded-lg gap-3 p-3 place-content-center"
|
||||
>
|
||||
<div className="flex justify-between content-center">
|
||||
<div className="text-headers text-p forecasts">{item.time}</div>
|
||||
<div className="text-headers text-p forecasts">{item.temperature}°</div>
|
||||
<div className="text-headers text-p para">{item.time}</div>
|
||||
<div className="text-headers text-p para">{item.temperature}°</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -24,26 +24,48 @@ function TimeCard() {
|
||||
|
||||
const formatDate = (date: Date) => {
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const ending = findDateEnding(date);
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const monthText = date.toLocaleString("default", { month: "long" });
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day}.${month}.${year}`;
|
||||
}
|
||||
return `${day}${ending} ${monthText} ${year}`;
|
||||
};
|
||||
|
||||
const day = (date: Date) => {
|
||||
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
const days = [
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
];
|
||||
return days[date.getDay()];
|
||||
}
|
||||
};
|
||||
|
||||
const findDateEnding = (date: Date) => {
|
||||
const day = date.getDate();
|
||||
|
||||
if (day === 1 || day === 21 || day === 31) {
|
||||
return "st";
|
||||
} else if (day === 2 || day === 22) {
|
||||
return "nd";
|
||||
} else if (day === 3 || day === 23) {
|
||||
return "rd";
|
||||
} else {
|
||||
return "th";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col bg-foreground rounded-lg p-3">
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="flex flex-1 flex-row bg-primary rounded-lg p-3 justify-evenly items-center">
|
||||
<p>{formatDate(date)}</p>
|
||||
<div className="text-headers text-p time">
|
||||
{formatTime(time)}
|
||||
</div>
|
||||
<p>{day(date)}</p>
|
||||
<div className="flex flex-1 flex-row bg-primary rounded-lg p-5 pl-3 pr-3 justify-between items-center">
|
||||
<p className="text-p para">{formatDate(date)}</p>
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 text-center text-headers text-p time">{formatTime(time)}</div>
|
||||
<p className="text-p para">{day(date)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const SanityEvents = () => {
|
||||
const [events, setEvents] = useState([]);
|
||||
|
||||
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;
|
||||
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const query = encodeURIComponent('*[_type == "event"]');
|
||||
const url = `https://${projectId}.api.sanity.io/v1/data/query/${dataset}?query=${query}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
setEvents(data.result);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [projectId, dataset]);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
export default SanityEvents;
|
||||
+12
-7
@@ -7,6 +7,7 @@
|
||||
--foreground: #261F31;
|
||||
--primary: #392e49;
|
||||
--headers: #D64097;
|
||||
--overlay: #4A3B61;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -15,6 +16,7 @@
|
||||
--foreground: #261F31;
|
||||
--primary: #392e49;
|
||||
--headers: #D64097;
|
||||
--overlay: #4A3B61;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,21 +44,24 @@ p {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.forecasts {
|
||||
.para {
|
||||
font-family: var(--font-nunito);
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.parabold {
|
||||
font-family: var(--font-nunito);
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-family: var(--font-nunito);
|
||||
font-weight: 800;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
font-family: var(--font-nunito);
|
||||
font-weight: 800;
|
||||
font-size: 2rem;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
|
||||
+15
-11
@@ -73,14 +73,16 @@ export default function Home() {
|
||||
const currentTime = new Date();
|
||||
const timeseries = data.properties.timeseries;
|
||||
|
||||
const nearest = data.properties.timeseries.reduce((prev: { time: string }, curr: { time: string }) => {
|
||||
const prevTime = new Date(prev.time);
|
||||
const currTime = new Date(curr.time);
|
||||
return Math.abs(currTime.getTime() - currentTime.getTime()) <
|
||||
Math.abs(prevTime.getTime() - currentTime.getTime())
|
||||
? curr
|
||||
: prev;
|
||||
});
|
||||
const nearest = data.properties.timeseries.reduce(
|
||||
(prev: { time: string }, curr: { time: string }) => {
|
||||
const prevTime = new Date(prev.time);
|
||||
const currTime = new Date(curr.time);
|
||||
return Math.abs(currTime.getTime() - currentTime.getTime()) <
|
||||
Math.abs(prevTime.getTime() - currentTime.getTime())
|
||||
? curr
|
||||
: prev;
|
||||
}
|
||||
);
|
||||
|
||||
setnearestHour(nearest.time);
|
||||
|
||||
@@ -146,10 +148,12 @@ export default function Home() {
|
||||
wind={currentWind}
|
||||
fiveHourForecast={nextThreeHours}
|
||||
/>
|
||||
<EventCard></EventCard>
|
||||
<EventCard
|
||||
recurringTimeAhead="96"
|
||||
specialTimeAhead="96"
|
||||
placeTimeAhead="4"
|
||||
></EventCard>
|
||||
<TimeCard></TimeCard>
|
||||
|
||||
<div className="flex-1 bg-green-500"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const SanityPeople = () => {
|
||||
const [people, setPeople] = useState([]);
|
||||
|
||||
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID;
|
||||
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const query = encodeURIComponent('*[_type == "author"]');
|
||||
const url = `https://${projectId}.api.sanity.io/v1/data/query/${dataset}?query=${query}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
setPeople(data.result);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [projectId, dataset]);
|
||||
|
||||
return people;
|
||||
}
|
||||
|
||||
export default SanityPeople;
|
||||
File renamed without changes.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
@@ -14,6 +14,7 @@ const config: Config = {
|
||||
foreground: "var(--foreground)",
|
||||
primary: "var(--primary)",
|
||||
headers: "var(--headers)",
|
||||
overlay: "var(--overlay)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user