Skip to content

Commit

Permalink
Merge pull request #510 from sparcs-kaist/506-edit-vote
Browse files Browse the repository at this point in the history
feat: add edit vote feature
  • Loading branch information
rjsdn0 authored Nov 4, 2024
2 parents 285e091 + f2bc461 commit abe0ea9
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 182 deletions.
7 changes: 6 additions & 1 deletion packages/api/src/listener/agenda.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as schema from "@biseo/interface/agenda";
import { retrieveAll, vote } from "@biseo/api/service/agenda";
import { retrieveAll, editVote, vote } from "@biseo/api/service/agenda";

import { Router } from "@biseo/api/lib/listener";

Expand All @@ -9,6 +9,11 @@ router.on("agenda.retrieveAll", schema.RetrieveAll, async (_, { user }) =>
retrieveAll(user),
);

router.on("agenda.edit", schema.EditVote, async (req, { io, user }) => {
await editVote(req, io, user);
return {};
});

router.on("agenda.vote", schema.Vote, async (req, { io, user }) => {
await vote(req, io, user);
return {};
Expand Down
48 changes: 48 additions & 0 deletions packages/api/src/service/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,54 @@ export const retrieveAll = async (
return res;
};

export const editVote = async (
{ choiceId, agendaId }: schema.Vote,
io: BiseoServer,
user: User,
) => {
const existingVote = await prisma.userChoice.findFirst({
where: {
userId: user.id,
choice: {
agendaId,
},
},
});

if (!existingVote) throw new BiseoError("No previous vote found");

await prisma.userChoice.delete({
where: {
userId_choiceId: {
userId: user.id,
choiceId: existingVote.choiceId,
},
},
});

await prisma.userChoice.create({
data: {
userId: user.id,
choiceId,
},
});

io.to(`user/${user.username}`).emit("agenda.voted", {
id: agendaId,
user: { voted: choiceId },
voters: {
voted: await prisma.userChoice.count({
where: {
choice: { agendaId },
},
}),
total: await prisma.userAgendaVotable.count({
where: { agendaId },
}),
},
});
};

export const vote = async (
{ choiceId, agendaId }: schema.Vote,
io: BiseoServer,
Expand Down
12 changes: 12 additions & 0 deletions packages/interface/src/agenda/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ export type RetrieveAll = z.infer<typeof RetrieveAll>;
export const RetrieveAllCb = z.array(Agenda);
export type RetrieveAllCb = z.infer<typeof RetrieveAllCb>;

/**
* Edit Vote
* description
*/
export const EditVote = z.object({
choiceId: z.number(),
agendaId: z.number(),
});
export type EditVote = z.infer<typeof EditVote>;
export const EditVoteCb = z.object({});
export type EditVoteCb = z.infer<typeof EditVoteCb>;

/**
* Vote
* description
Expand Down
2 changes: 2 additions & 0 deletions packages/interface/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type ClientToServerEvents = Events<{
agenda: {
retrieveAll: Ev<agenda.RetrieveAll, agenda.RetrieveAllCb>;
vote: Ev<agenda.Vote, agenda.VoteCb>;
edit: Ev<agenda.EditVote, agenda.EditVoteCb>;
template: {
create: Ev<agendaTemplate.Create, agendaTemplate.CreateCb>;
retrieveAll: Ev<agendaTemplate.RetrieveAll, agendaTemplate.RetrieveAllCb>;
Expand Down Expand Up @@ -59,6 +60,7 @@ export type ServerToClientEvents = Events<{
updated: Ev<agenda.Updated>;
started: Ev<agenda.Started>;
voted: Ev<agenda.Voted>;
voteEdited: Ev<agenda.Voted>;
terminated: Ev<agenda.Terminated>;
deleted: Ev<agenda.Deleted>;
reminded: Ev<agenda.Reminded>;
Expand Down
11 changes: 10 additions & 1 deletion packages/web/src/components/atoms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ export const Button = styled.button<{
w?: Size;
h?: Size;
color?: Color;
textColor?: Color;
padHorizontal?: number;
}>(
({ w = "fill", h = 28, color = "blue200", padHorizontal = 0, theme }) => css`
({
w = "fill",
h = 28,
color = "blue200",
textColor = "black",
padHorizontal = 0,
theme,
}) => css`
display: flex;
width: ${calcSize(w)};
height: ${calcSize(h)};
Expand All @@ -26,6 +34,7 @@ export const Button = styled.button<{
align-items: center;
line-height: 28px;
background-color: ${theme.colors[color]};
color: ${theme.colors[textColor]};
&:hover {
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ const agendaTags = {

export const OngoingAgendaCard: React.FC<OngoingAgendaProps> = ({ agenda }) => {
const [chosenChoiceId, setChosenChoiceId] = useState(0);
const { voteAgenda } = useAgenda(state => ({
const [isEditing, setIsEditing] = useState(false);
const { voteAgenda, editVote } = useAgenda(state => ({
voteAgenda: state.voteAgenda,
editVote: state.editVote,
}));

const vote = useCallback(() => {
Expand All @@ -47,14 +49,20 @@ export const OngoingAgendaCard: React.FC<OngoingAgendaProps> = ({ agenda }) => {
[chosenChoiceId],
);

const submitEdit = useCallback(() => {
editVote(chosenChoiceId, agenda.id);
setIsEditing(false);
}, [chosenChoiceId, agenda.id]);

let choices: JSX.Element | JSX.Element[] = <NotVotableChoice />;
if (agenda.user.votable) {
if (agenda.user.voted) {
if (agenda.user.voted && !isEditing) {
choices = (
<CompletedChoice
choice={agenda.choices.find(
choice => choice.id === agenda.user.voted,
)}
onEdit={() => setIsEditing(true)}
/>
);
} else {
Expand Down Expand Up @@ -85,9 +93,20 @@ export const OngoingAgendaCard: React.FC<OngoingAgendaProps> = ({ agenda }) => {
<p css={[text.subtitle, text.gray500]}>{agenda.content}</p>
</div>
</div>
<div css={[column, gap(6)]}>
<p css={[text.body, text.blue600]}>{agenda.resolution}</p>
{choices}
<div css={[column, gap(10)]}>
<div css={[column, gap(6)]}>
<p css={[text.body, text.blue600]}>{agenda.resolution}</p>
{choices}
</div>
{isEditing && (
<div css={[row, justify.end, w("fill")]}>
<Button w={90} disabled={!chosen} onClick={submitEdit}>
<p css={[text.option1, chosen ? text.blue600 : text.blue300]}>
제출하기
</p>
</Button>
</div>
)}
</div>
{agenda.user.votable && !agenda.user.voted && (
<div css={[row, justify.end, w("fill")]}>
Expand Down
53 changes: 37 additions & 16 deletions packages/web/src/components/molecules/Choice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import styled from "@emotion/styled";
import { css } from "@emotion/react";

import type { Choice } from "@biseo/interface/agenda";
import { Text } from "@biseo/web/components/atoms";
import { Text, Button } from "@biseo/web/components/atoms";
import { SelectIcon } from "@biseo/web/assets";
import { type Color, theme } from "@biseo/web/theme";
import { center, h, w } from "@biseo/web/styles";
import {
gap,
column,
center,
row,
justify,
text,
h,
w,
} from "@biseo/web/styles";

const Container = styled.div<{
color: Color;
Expand Down Expand Up @@ -69,6 +78,7 @@ interface ChoiceBaseProps {

const ChoiceBase: React.FC<ChoiceBaseProps> = ({
variant,
// eslint-disable-next-line @typescript-eslint/no-shadow
text,
onClick = () => {},
onMouseEnter = () => {},
Expand Down Expand Up @@ -112,27 +122,38 @@ export const ChoiceComponent: React.FC<ChoiceProps> = ({

interface CompletedChoiceProps {
choice?: Choice;
onEdit: () => void;
}

export const CompletedChoice: React.FC<CompletedChoiceProps> = ({
choice = undefined,
onEdit,
}) => {
const [hover, setHover] = useState(false);

return hover ? (
<ChoiceBase
variant="hover"
text={choice?.name ?? ""}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
/>
) : (
<ChoiceBase
variant="chosen"
text="투표 완료"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
/>
return (
<div css={[column, gap(10)]}>
{hover ? (
<ChoiceBase
variant="hover"
text={choice?.name ?? ""}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
/>
) : (
<ChoiceBase
variant="chosen"
text="투표 완료"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
/>
)}
<div css={[row, justify.end, w("fill")]}>
<Button w={90} onClick={onEdit}>
<p css={[text.option1, text.blue600]}>수정하기</p>
</Button>
</div>
</div>
);
};

Expand Down
8 changes: 8 additions & 0 deletions packages/web/src/services/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { socket } from "@biseo/web/socket";
interface AgendaState {
agendas: Agenda[];
voteAgenda: (choiceId: number, agendaId: number) => void;
editVote: (choiceId: number, agendaId: number) => void;
retrieveAgendas: () => void;
}

Expand All @@ -17,6 +18,13 @@ const useAgenda = create<AgendaState>(set => ({
// TODO: globally handle error using zustand middleware
}
},
editVote: async (choiceId, agendaId) => {
try {
await socket.emitAsync("agenda.edit", { choiceId, agendaId });
} catch (error) {
// TODO: globally handle error using zustand middleware
}
},
retrieveAgendas: async () => {
try {
const agendas = await socket.emitAsync("agenda.retrieveAll", {});
Expand Down
Loading

0 comments on commit abe0ea9

Please sign in to comment.