mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Restructure and add notes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
|
||||
import { monthsVotingRange } from "./core/plus";
|
||||
import type { User } from "./db/types";
|
||||
import type { PlusSuggestion, User } from "./db/types";
|
||||
import { allTruthy } from "./utils/arrays";
|
||||
|
||||
// TODO: 1) move "root checkers" to one file and utils to one file 2) make utils const for more terseness
|
||||
@@ -39,13 +39,34 @@ export function canAddCommentToSuggestionBE({
|
||||
}
|
||||
|
||||
interface CanDeleteCommentArgs {
|
||||
suggestionId: PlusSuggestion["id"];
|
||||
author: Pick<User, "id">;
|
||||
user?: Pick<User, "id">;
|
||||
suggestions: plusSuggestions.FindVisibleForUser;
|
||||
}
|
||||
export function canDeleteComment(args: CanDeleteCommentArgs) {
|
||||
if (isFirstSuggestion(args)) {
|
||||
return allTruthy([!isVotingActive(), isOwnComment(args)]);
|
||||
}
|
||||
|
||||
return isOwnComment(args);
|
||||
}
|
||||
|
||||
function isFirstSuggestion({
|
||||
suggestionId,
|
||||
suggestions,
|
||||
}: Pick<CanDeleteCommentArgs, "suggestionId" | "suggestions">) {
|
||||
for (const suggestedUser of Object.values(suggestions).flat()) {
|
||||
for (const [i, suggestion] of suggestedUser.suggestions.entries()) {
|
||||
if (suggestion.id !== suggestionId) continue;
|
||||
|
||||
return i === 0;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Invalid suggestion id: ${suggestionId}`);
|
||||
}
|
||||
|
||||
function alreadyCommentedByUser({
|
||||
user,
|
||||
suggestions,
|
||||
@@ -92,7 +113,7 @@ export function canSuggestNewUserFE({
|
||||
suggestions,
|
||||
}: CanSuggestNewUserFEArgs) {
|
||||
return allTruthy([
|
||||
!votingIsActive(),
|
||||
!isVotingActive(),
|
||||
!hasUserSuggestedThisMonth({ user, suggestions }),
|
||||
isPlusServerMember(user),
|
||||
]);
|
||||
@@ -116,7 +137,7 @@ export function canSuggestNewUserBE({
|
||||
]);
|
||||
}
|
||||
|
||||
function votingIsActive() {
|
||||
function isVotingActive() {
|
||||
const now = new Date();
|
||||
const { endDate, startDate } = monthsVotingRange({
|
||||
month: now.getMonth(),
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TrashIcon } from "~/components/icons/Trash";
|
||||
import { upcomingVoting } from "~/core/plus";
|
||||
import { db } from "~/db";
|
||||
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
|
||||
import type { PlusSuggestion } from "~/db/types";
|
||||
import { useUser } from "~/hooks/useUser";
|
||||
import {
|
||||
canAddCommentToSuggestionFE,
|
||||
@@ -69,8 +70,16 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
.find((s) => s.id === data.suggestionId)
|
||||
: undefined;
|
||||
|
||||
validate(suggestions);
|
||||
validate(targetSuggestion);
|
||||
validate(canDeleteComment({ user, author: targetSuggestion.author }));
|
||||
validate(
|
||||
canDeleteComment({
|
||||
user,
|
||||
author: targetSuggestion.author,
|
||||
suggestionId: data.suggestionId,
|
||||
suggestions,
|
||||
})
|
||||
);
|
||||
|
||||
db.plusSuggestions.del(data.suggestionId);
|
||||
|
||||
@@ -224,6 +233,7 @@ function SuggestedUser({
|
||||
discordId={suggested.info.discordId}
|
||||
size="md"
|
||||
/>
|
||||
{/* xxx: can cause page to overflow when long e.g. Buckinghamshire */}
|
||||
<h2>{suggested.info.discordName}</h2>
|
||||
{canAddCommentToSuggestionFE({
|
||||
user,
|
||||
@@ -251,38 +261,67 @@ function SuggestedUser({
|
||||
Comments ({suggested.suggestions.length})
|
||||
</summary>
|
||||
<div className="stack sm mt-2">
|
||||
{suggested.suggestions.map((s) => (
|
||||
<fieldset key={s.id} className="plus__comment">
|
||||
<legend>{discordFullName(s.author)}</legend>
|
||||
{s.text}
|
||||
<div className="stack vertical xs items-center">
|
||||
<span className="plus__comment-time">
|
||||
<time>
|
||||
{databaseTimestampToDate(s.createdAt).toLocaleString()}
|
||||
</time>
|
||||
</span>
|
||||
{canDeleteComment({ author: s.author, user }) ? (
|
||||
<FormWithConfirm
|
||||
fields={[["suggestionId", s.id]]}
|
||||
dialogHeading={`Delete your comment to ${suggested.info.discordName}'s +${tier} suggestion?`}
|
||||
>
|
||||
{/* xxx: what when this is the first suggestion... it would trigger different behavior so confusing maybe? */}
|
||||
<Button
|
||||
className="plus__delete-button"
|
||||
icon={<TrashIcon />}
|
||||
variant="minimal-destructive"
|
||||
aria-label="Delete comment"
|
||||
data-cy="delete-comment-button"
|
||||
{suggested.suggestions.map((suggestion) => {
|
||||
invariant(data.suggestions);
|
||||
return (
|
||||
<fieldset key={suggestion.id} className="plus__comment">
|
||||
<legend>{discordFullName(suggestion.author)}</legend>
|
||||
{suggestion.text}
|
||||
<div className="stack vertical xs items-center">
|
||||
<span className="plus__comment-time">
|
||||
<time>
|
||||
{databaseTimestampToDate(
|
||||
suggestion.createdAt
|
||||
).toLocaleString()}
|
||||
</time>
|
||||
</span>
|
||||
{canDeleteComment({
|
||||
author: suggestion.author,
|
||||
user,
|
||||
suggestionId: suggestion.id,
|
||||
suggestions: data.suggestions,
|
||||
}) ? (
|
||||
<CommentDeleteButton
|
||||
suggestionId={suggestion.id}
|
||||
tier={tier}
|
||||
suggestedDiscordName={suggested.info.discordName}
|
||||
/>
|
||||
</FormWithConfirm>
|
||||
) : null}
|
||||
</div>
|
||||
</fieldset>
|
||||
))}
|
||||
) : null}
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CommentDeleteButton({
|
||||
suggestionId,
|
||||
tier,
|
||||
suggestedDiscordName,
|
||||
}: {
|
||||
suggestionId: PlusSuggestion["id"];
|
||||
tier: string;
|
||||
suggestedDiscordName: string;
|
||||
}) {
|
||||
return (
|
||||
<FormWithConfirm
|
||||
fields={[["suggestionId", suggestionId]]}
|
||||
// TODO: Delete your suggestion of suggestedDiscordName and 3 comments to it? + different behavior of the delete event
|
||||
dialogHeading={`Delete your comment to ${suggestedDiscordName}'s +${tier} suggestion?`}
|
||||
>
|
||||
{/* xxx: what when this is the first suggestion... it would trigger different behavior so confusing maybe? */}
|
||||
<Button
|
||||
className="plus__delete-button"
|
||||
icon={<TrashIcon />}
|
||||
variant="minimal-destructive"
|
||||
aria-label="Delete comment"
|
||||
data-cy="delete-comment-button"
|
||||
/>
|
||||
</FormWithConfirm>
|
||||
);
|
||||
}
|
||||
|
||||
export const CatchBoundary = Catcher;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ActionFunction } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import { Form, useMatches, useNavigate, useParams } from "@remix-run/react";
|
||||
import { Form, useMatches, useParams } from "@remix-run/react";
|
||||
import { z } from "zod";
|
||||
import { Button, LinkButton } from "~/components/Button";
|
||||
import { Dialog } from "~/components/Dialog";
|
||||
@@ -42,7 +42,8 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
canAddCommentToSuggestionBE({
|
||||
suggestions,
|
||||
user,
|
||||
suggested: { id: data.suggestedId, plusTier: data.tier },
|
||||
suggested: { id: data.suggestedId },
|
||||
targetPlusTier: data.tier,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -58,7 +59,6 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
export default function PlusCommentModalPage() {
|
||||
const user = useUser();
|
||||
const matches = useMatches();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const data = matches.at(-2)!.data as PlusSuggestionsLoaderData;
|
||||
|
||||
@@ -75,7 +75,8 @@ export default function PlusCommentModalPage() {
|
||||
!canAddCommentToSuggestionFE({
|
||||
user,
|
||||
suggestions: data.suggestions,
|
||||
suggested: { id: targetUserId, plusTier: Number(tierSuggestedTo) },
|
||||
suggested: { id: targetUserId },
|
||||
targetPlusTier: Number(tierSuggestedTo),
|
||||
})
|
||||
) {
|
||||
return <Redirect to={PLUS_SUGGESTIONS_PAGE} />;
|
||||
|
||||
@@ -26,6 +26,7 @@ export async function parseRequestFormData<T extends z.ZodTypeAny>({
|
||||
}: {
|
||||
request: Request;
|
||||
schema: T;
|
||||
// xxx: get rid of this
|
||||
useBody?: boolean;
|
||||
}): Promise<z.infer<T>> {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user