Skip to content

Commit

Permalink
add: vote record by you
Browse files Browse the repository at this point in the history
  • Loading branch information
holenet committed Aug 28, 2024
1 parent 9c8a830 commit 7ee7ab5
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 58 deletions.
53 changes: 46 additions & 7 deletions LDLWebClient/src/TopicEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useSignal } from "@preact/signals";
import { useComputed, useSignal } from "@preact/signals";
import { useEffect, useRef } from "preact/hooks";
import classNames from "classnames";
import { Topic } from "./model";
import { useLocalStorageState } from "./hooks";

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
const COLORS = ["bg-red-400", "bg-blue-400", "bg-green-400", "bg-orange-400", "bg-purple-400", "bg-yellow-400"];
const BG_COLORS = ["bg-red-300", "bg-blue-300", "bg-green-300", "bg-orange-300", "bg-purple-300", "bg-yellow-200"];
const COLORS = ["bg-red-500", "bg-blue-500", "bg-green-500", "bg-orange-500", "bg-purple-500", "bg-yellow-500"];

type Props = {
topic: Topic;
Expand All @@ -13,6 +15,15 @@ type Props = {
export function TopicEntry({ topic: _topic, colorOffset }: Props) {
const connectedWebSocket = useRef<WebSocket>();
const topic = useSignal<Topic>();
const votePercentages = useComputed(() => {
const sum = topic.value.Votes.reduce((a, x) => a + x, 0);
if (sum === 0) return topic.value.Votes.map((_) => 0);
return topic.value.Votes.map((v) => v / sum);
});
const [voteRecord, setVoteRecord] = useLocalStorageState<number[]>(
`vote-record-${_topic.Id}`,
_topic.Votes.map((_) => 0)
);

useEffect(() => {
topic.value = _topic;
Expand Down Expand Up @@ -61,26 +72,54 @@ export function TopicEntry({ topic: _topic, colorOffset }: Props) {
...topic.value,
Votes: topic.value.Votes.map((v, i) => Math.max(v, votes[i])),
};
setVoteRecord(voteRecord.map((v, i) => (i === index ? v + 1 : v)));
});
};

return (
<div class="flex flex-col w-full h-full justify-center items-center">
{topic.value && (
<div class="flex flex-col w-full gap-6">
<div class="flex flex-col w-full gap-2">
<div class="font-bold text-lg text-neutral-700 text-wrap w-full break-words">{topic.value.Content}</div>
<div class="flex flex-col gap-1">
{topic.value.Choices.map((choice, i) => (
<>
{i !== 0 && <span class="opacity-80">vs</span>}
<button
className={classNames(
"rounded-full shadow-md px-8 py-2 font-bold text-white hover:scale-110 transition-all",
COLORS[(i + colorOffset) % COLORS.length]
"rounded-xl shadow-md px-4 py-2 hover:scale-105 transition-all flex flex-col items-stretch gap-1 relative overflow-hidden",
BG_COLORS[(i + colorOffset) % COLORS.length]
)}
onClick={() => vote(i)}
>
{choice}: {topic.value.Votes[i]}
<div
className={classNames(
"absolute top-0 left-0 h-full w-full z-10",
COLORS[(i + colorOffset) % COLORS.length]
)}
style={{
transition: "transform 750ms, background-color 150ms",
transform: `translateX(${-100 + votePercentages.value[i] * 100}%)`,
filter: "saturate(75%) brightness(120%)",
}}
></div>
<div class="h-full w-full z-20" style="text-shadow: 0 0 2px #00000042">
<div class="font-bold text-white text-start">{choice}</div>
<div class="flex flex-row items-baseline flex-wrap justify-between">
<div class="font-bold text-white ">
{topic.value.Votes[i]}
<span class="text-xs ml-0.5">Votes</span>
</div>
<div className={"font-bold text-white"}>{(votePercentages.value[i] * 100).toFixed(1)}%</div>
</div>
<div
className={classNames("text-white text-xs font-bold text-start pb-1", {
"opacity-0": voteRecord[i] === 0,
"opacity-85": voteRecord[i] > 0,
})}
>
+{voteRecord[i]} by you
</div>
</div>
</button>
</>
))}
Expand Down
66 changes: 66 additions & 0 deletions LDLWebClient/src/TopicForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useState } from "preact/hooks";
import { Topic } from "./model";

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

type Props = {
onTopicAdded?: (topic: Topic) => any;
};
export function TopicForm({ onTopicAdded }: Props) {
const [contentForm, setContentForm] = useState("");
const [choiceAForm, setChoiceAForm] = useState("");
const [choiceBForm, setChoiceBForm] = useState("");

const addTopic = async (e: Event) => {
e.preventDefault();
const url = `${API_BASE_URL}/topic`;
const res = await fetch(url, {
method: "POST",
body: JSON.stringify({
Content: contentForm,
Choices: [choiceAForm, choiceBForm],
}),
});
setContentForm("");
setChoiceAForm("");
setChoiceBForm("");

const topic = await res.json();
onTopicAdded?.(topic);
};

return (
<form class="flex flex-col gap-1">
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">토픽:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={contentForm}
onInput={(e) => setContentForm(e.currentTarget.value)}
/>
</div>
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">선택지A:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={choiceAForm}
onInput={(e) => setChoiceAForm(e.currentTarget.value)}
/>
</div>
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">선택지B:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={choiceBForm}
onInput={(e) => setChoiceBForm(e.currentTarget.value)}
/>
</div>
<button
class="rounded-md shadow-md bg-green-300 p-1 mt-4 font-bold text-sm text-neutral-600 transition-all hover:scale-[1.03]"
onClick={addTopic}
>
추가하기
</button>
</form>
);
}
19 changes: 19 additions & 0 deletions LDLWebClient/src/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useMemo, useState } from "preact/hooks";

export function useLocalStorageState<T>(key: string, defaultValue: T): [T, (v: T) => any] {
const [_value, _setValue] = useState<T>();

useEffect(() => {
const v = localStorage.getItem(key);
_setValue(v !== null ? JSON.parse(v) : defaultValue);
}, [key]);

const setValue = useMemo(() => {
return (v: T) => {
_setValue(v);
localStorage.setItem(key, JSON.stringify(v));
};
}, [key]);

return [_value, setValue];
}
55 changes: 4 additions & 51 deletions LDLWebClient/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useSignal } from "@preact/signals";
import { render } from "preact";
import { useEffect, useState } from "preact/hooks";
import { useEffect, useMemo, useState } from "preact/hooks";
import "./style.css";
import { Topic } from "./model";
import { TopicEntry } from "./TopicEntry";
import { TopicForm } from "./TopicForm";

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

Expand Down Expand Up @@ -32,26 +33,9 @@ function App() {
}
};

const [contentForm, setContentForm] = useState("");
const [choiceAForm, setChoiceAForm] = useState("");
const [choiceBForm, setChoiceBForm] = useState("");

const addTopic = async (e: Event) => {
e.preventDefault();
const url = `${API_BASE_URL}/topic`;
const res = await fetch(url, {
method: "POST",
body: JSON.stringify({
Content: contentForm,
Choices: [choiceAForm, choiceBForm],
}),
});
const topic = await res.json();
const onTopicAdded = (topic: Topic) => {
topics.value = [...topics.value, topic];
currentIndex.value = topics.value.length - 1;
setContentForm("");
setChoiceAForm("");
setChoiceBForm("");
};

return (
Expand Down Expand Up @@ -89,38 +73,7 @@ function App() {
</div>
)}
<div class="shadow-md bg-white rounded-2xl p-4">
<form class="flex flex-col gap-1">
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">토픽:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={contentForm}
onInput={(e) => setContentForm(e.currentTarget.value)}
/>
</div>
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">선택지A:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={choiceAForm}
onInput={(e) => setChoiceAForm(e.currentTarget.value)}
/>
</div>
<div class="flex gap-1">
<div class="w-16 text-right text-neutral-600">선택지B:</div>
<input
class="outline-none border border-neutral-300 rounded-md shadow-sm px-1"
value={choiceBForm}
onInput={(e) => setChoiceBForm(e.currentTarget.value)}
/>
</div>
<button
class="rounded-md shadow-md bg-green-300 p-1 mt-4 font-bold text-sm text-neutral-600 transition-all hover:scale-[1.03]"
onClick={addTopic}
>
추가하기
</button>
</form>
<TopicForm onTopicAdded={onTopicAdded} />
</div>
</div>
);
Expand Down

0 comments on commit 7ee7ab5

Please sign in to comment.