Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
polamoros committed Aug 11, 2023
1 parent 81f7aa9 commit a76e6db
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 46 deletions.
6 changes: 4 additions & 2 deletions website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ function App() {
return (
<div className="App absolute inset-0">
<div className="w-full h-full flex flex-col items-center justify-center">
<h1 className="title text-7xl pb-7 font-medium">Battle of the Bites!</h1>
<div className="text-2xl w-[30rem] min-h-[35rem] px-10 pt-10 pb-4 bg-sky-200 rounded-lg shadow-xl flex flex-col">
<h1 className="title text-7xl pb-7 font-medium">
Battle of the Bites!
</h1>
<div className="text-2xl w-[30rem] min-h-[35rem] px-8 pt-8 pb-4 bg-sky-200 rounded-lg shadow-xl flex flex-col">
<div className="text-slate-700 text-4xl text-center">
{view === "voting" ? "Which is better?" : "Leaderboard"}
</div>
Expand Down
74 changes: 51 additions & 23 deletions website/src/components/VoteItem.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button } from "./Button";
import classnames from "classnames";
import { SpinnerLoader } from "./SpinnerLoader";
import { useMemo } from "react";

export interface VoteItemProps {
name: string;
imageUrl: string;
image?: string;
onClick: () => void;
disabled?: boolean;
loading?: boolean;
Expand All @@ -15,27 +16,64 @@ export interface VoteItemProps {
export const VoteItem = ({
name,
onClick,
imageUrl,
image,
disabled,
loading,
winner,
score,
}: VoteItemProps) => {
const rotateStyle = useMemo(() => {
return {
transform: winner && "rotateY(180deg)",
};
}, [winner]);

return (
<div className="text-center">
<h3 className="text-3xl truncate h-8 mb-5 text-slate-700">{name}</h3>
<div className="relative w-32 h-32 mx-auto rounded-lg truncate">
<div className="w-full h-full bg-sky-100 animate-pulse absolute shadow items-center justify-center flex opacity-50">
<SpinnerLoader />
</div>
{imageUrl !== "" && (
<img
className="w-full h-full object-fill absolute z-10"
src={imageUrl}
alt={name}
/>
<div
className={classnames(
"rounded-lg bg-white",
"transition-transform duration-300 w-full h-full transform"
)}
style={rotateStyle}
>
<h3
className="text-3xl truncate py-2 text-slate-700 px-2 h-12"
style={rotateStyle}
>
{winner && (winner === name ? "🥇" : "🥈")}
{!winner && name}
</h3>

<div
className="relative h-36 mx-auto rounded-b-lg truncate border-t-2 border-slate-500 bg-sky-100"
style={rotateStyle}
>
<div className="absolute inset-0 flex items-center justify-center opacity-50">
<SpinnerLoader />
</div>

{winner && (
<div
className={classnames(
"w-full h-full absolute z-10",
winner === name ? "bg-green-100" : "bg-red-100"
)}
>
Score: {Math.max(score!, 0)}
</div>
)}

{!winner && image && (
<img
className="w-full h-full object-cover absolute z-10"
src={`${image}`}
alt={name}
/>
)}
</div>
</div>

<div className="pt-6">
{!winner && (
<Button
Expand All @@ -45,16 +83,6 @@ export const VoteItem = ({
disabled={disabled}
/>
)}
{winner && (
<div
className={classnames(
"h-8",
winner === name ? "text-green-600" : "text-red-600"
)}
>
{winner === name ? "🥇" : "👎"} (Score: {Math.max(score!, 0)})
</div>
)}
</div>
</div>
);
Expand Down
30 changes: 28 additions & 2 deletions website/src/services/fetchChoices.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
import { fetchConfig } from "./fetchConfig";

export interface Choice {
label: string;
imageSvg?: string;
}

const getImageSvg = async (label: string): Promise<string> => {
const url = `https://source.unsplash.com/featured/128x128/?${label}&category=food`;
const response = await fetch(url);
const blob = await response.blob();
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result as string);
};
reader.onerror = reject;
reader.readAsDataURL(blob);
});
};

export const fetchChoices = async () => {
const apiUrl = (await fetchConfig()).apiUrl;
const response = await fetch(apiUrl + "/requestChoices", {
Expand All @@ -8,6 +27,13 @@ export const fetchChoices = async () => {
if (!response.ok) {
throw new Error("Failed to request choices");
}
const jsonData: string[] = await response.json();
return jsonData;
const labels: string[] = await response.json();

const choices = await Promise.all(
labels.map(async (label) => {
const imageSvg = await getImageSvg(label);
return { label, imageSvg };
})
);
return choices;
};
2 changes: 1 addition & 1 deletion website/src/views/LeaderboardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const LeaderboardView = () => {
}, []);

return (
<div className="max-h-[23rem] overflow-y-auto h-full">
<div className="max-h-[22rem] overflow-y-auto h-full">
<table className="divide-y divide-slate-400 min-w-[20rem] h-full">
<thead>
<tr>
Expand Down
37 changes: 19 additions & 18 deletions website/src/views/VotingView.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { useEffect, useState } from "react";
import { Button } from "../components/Button";
import { fetchChoices } from "../services/fetchChoices";
import { Choice, fetchChoices } from "../services/fetchChoices";
import { submitVote } from "../services/submitVote";
import { VoteItem } from "../components/VoteItem";

export const VotingView = () => {
const [choices, setChoices] = useState<string[]>(["", ""]);
const [choices, setChoices] = useState<Choice[]>([
{ label: "" },
{ label: "" },
]);
const [scores, setScores] = useState<number[]>([]);

const [loading, setLoading] = useState(true);

const [selectedWinnerIdx, setSelectedWinnerIdx] = useState<number>();
const [loadingScores, setLoadingScores] = useState(false);

useEffect(() => {
Expand All @@ -21,49 +23,48 @@ export const VotingView = () => {
}, []);

const [winner, setWinner] = useState<string>();
const selectWinner = async (winner: string) => {
const loser = choices.find((choice) => choice !== winner)!;
const [selectedChoice, setSelectedChoice] = useState<Choice>();

const selectWinner = async (winner: Choice) => {
setSelectedChoice(winner);
const loser = choices.find((choice) => choice.label !== winner.label)!;

setLoadingScores(true);
setSelectedWinnerIdx(choices.indexOf(winner));
const { winner: winnerScore, loser: loserScore } = await submitVote(
winner,
loser
winner.label,
loser.label
);
if (winner === choices[0]) {
setScores([winnerScore, loserScore]);
} else {
setScores([loserScore, winnerScore]);
}
setWinner(winner);
setWinner(winner.label);
setLoadingScores(false);
};

const reset = async () => {
setWinner(undefined);
setLoading(true);
setScores([]);
setChoices([{ label: "" }, { label: "" }]);
const choices = await fetchChoices();
setChoices(choices);
setLoading(false);
};

return (
<div className="choices space-y-4">
<div className="flex gap-x-8">
<div className="flex">
{choices.map((choice, index) => (
<div className="w-1/2">
<div className="w-1/2 shrink-0 px-4">
<VoteItem
key={index}
name={choice}
imageUrl={
loading
? ""
: `https://source.unsplash.com/featured/128x128/?${choice}&category=food`
}
name={choice.label}
image={loading ? undefined : choice.imageSvg}
onClick={() => selectWinner(choice)}
disabled={loading || loadingScores}
loading={loadingScores && selectedWinnerIdx === index}
loading={loadingScores && selectedChoice?.label === choice.label}
winner={winner}
score={Math.floor(scores[index])}
/>
Expand Down

0 comments on commit a76e6db

Please sign in to comment.