diff --git a/app/db/models/plusVotes.server.ts b/app/db/models/plusVotes.server.ts index aa17b0d3f..5af0991fb 100644 --- a/app/db/models/plusVotes.server.ts +++ b/app/db/models/plusVotes.server.ts @@ -249,3 +249,24 @@ export function usersForVoting( return shuffle(result.filter(({ user }) => user.id !== loggedInUser.id)); } + +const hasVotedStm = sql.prepare(` + SELECT 1 + FROM "PlusVote" + WHERE "authorId" = $userId AND "month" = $month AND "year" = $year +`); + +export function hasVoted({ + month, + year, + user, +}: MonthYear & { user?: Pick }) { + if (!user) return false; + return Boolean( + hasVotedStm.get({ + userId: user.id, + month, + year, + }) + ); +} diff --git a/app/routes/plus/voting/index.tsx b/app/routes/plus/voting/index.tsx index 5aedda0ad..aec95ed24 100644 --- a/app/routes/plus/voting/index.tsx +++ b/app/routes/plus/voting/index.tsx @@ -6,6 +6,7 @@ import * as React from "react"; import { z } from "zod"; import { Avatar } from "~/components/Avatar"; import { Button } from "~/components/Button"; +import { CheckmarkIcon } from "~/components/icons/Checkmark"; import { RelativeTime } from "~/components/RelativeTime"; import { PLUS_DOWNVOTE, PLUS_UPVOTE } from "~/constants"; import { db } from "~/db"; @@ -100,14 +101,13 @@ type PlusVotingLoaderData = relativeTime: string; timestamp: number; timing: "starts" | "ends"; + voted?: boolean; } // user can vote | { type: "voting"; usersForVoting: UsersForVoting; - } - // user already voted - | { type: "votingInfo"; votingInfo: { placeholder: true } }; + }; export const loader: LoaderFunction = async ({ request }) => { const user = await getUser(request); @@ -124,13 +124,18 @@ export const loader: LoaderFunction = async ({ request }) => { } const usersForVoting = db.plusVotes.usersForVoting(user); + const hasVoted = db.plusVotes.hasVoted({ + user, + ...nextNonCompletedVoting(new Date()), + }); - if (!usersForVoting) { + if (!usersForVoting || hasVoted) { return json({ type: "timeInfo", relativeTime: formatDistance(endDate, now, { addSuffix: true }), timestamp: endDate.getTime(), timing: "ends", + voted: hasVoted, }); } @@ -150,9 +155,6 @@ export default function PlusVotingPage() { case "voting": { return ; } - case "votingInfo": { - return null; - } default: { assertUnreachable(data); } @@ -163,13 +165,20 @@ function VotingTimingInfo( data: Extract ) { return ( -
- {data.timing === "starts" - ? "Next voting starts" - : "Voting is currently happening. Ends"}{" "} - - {data.relativeTime} - +
+ {data.voted ? ( +
+ You have voted +
+ ) : null} +
+ {data.timing === "starts" + ? "Next voting starts" + : "Voting is currently happening. Ends"}{" "} + + {data.relativeTime} + +
); } diff --git a/app/styles/global.css b/app/styles/global.css index d81fb0780..5a81b6f7e 100644 --- a/app/styles/global.css +++ b/app/styles/global.css @@ -17,6 +17,7 @@ --theme-warning: #f5f587; --theme-info: #a3a6ff; --theme-success: #a3ffae; + --theme-success-transparent: #a3ffae52; --theme: hsl(255deg 66.7% 75%); --theme-transparent: hsl(255deg 66.7% 75% / 40%); --theme-transparent-vibrant: hsl(255deg 78% 65% / 54%); @@ -29,7 +30,7 @@ --fonts-xl: 1.5rem; --fonts-lg: 1.2rem; --fonts-md: 1rem; - --fonts-sm: 0.9rem; + --fonts-sm: 0.92rem; --fonts-xs: 0.8rem; --fonts-xxs: 0.7rem; --extra-bold: 700; diff --git a/app/styles/plus.css b/app/styles/plus.css index 2fa274356..40cfa5681 100644 --- a/app/styles/plus.css +++ b/app/styles/plus.css @@ -123,3 +123,25 @@ .plus-voting__progress { width: 100%; } + +.plus-voting__alert { + display: flex; + align-items: center; + justify-content: center; + background-color: var(--theme-success-transparent); + border-radius: var(--rounded); + color: var(--text); + font-size: var(--fonts-sm); + font-weight: var(--semi-bold); + gap: var(--s-2); + line-height: 0.75; + margin-inline: auto; + padding-block: var(--s-1); + padding-inline-end: var(--s-4); + padding-inline-start: var(--s-3); +} + +.plus-voting__alert > svg { + height: 1.75rem; + fill: var(--theme-success); +} diff --git a/cypress/integration/plus.spec.ts b/cypress/integration/plus.spec.ts index f95290c59..f823ae492 100644 --- a/cypress/integration/plus.spec.ts +++ b/cypress/integration/plus.spec.ts @@ -69,4 +69,6 @@ describe("Plus voting results page", () => { cy.contains("Sendou"); cy.contains("your score was"); }); + + // xxx: describe Plus Voting }); diff --git a/plus-server-plan.md b/plus-server-plan.md deleted file mode 100644 index 929066153..000000000 --- a/plus-server-plan.md +++ /dev/null @@ -1,53 +0,0 @@ -- /plus - -> Shows if you are suggested or not -- CRUD suggestions for your own tier and below -- CUD disabled when voting is active - -- /plus/voting - -## Voting is active - --> If not member just show same view as when voting is not active - -- When voting the progress is saved in your browser so even if you leave the page you can continue where you left off. -- +1/-1 regardless of region - -## Voting is not active - -- You will see who passed the last voting and who didn't. -- If you didn't pass the voting and you were a suggestion then you will see your own percentage - - others see who passed and who didn't per tier - - at the top show your own results in green/red per tier - -## Tables - -- "User" - -* "plusTier" 1/2/3 - -- "PlusSuggestion" - -* "text" text -* "authorId" text -* "suggestedId" text -* "month" -* "year" -* "tier" 1/2/3 -* "createdAt" - -- "PlusVote" - -* "month" -* "year" -* "tier" -* "authorId" -* "votedId" -* "score" - -unique on month, year, authorId, votedId - -# Cypress - -- uses different db file -- uses different port -- NODE_ENV = test