Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,11 @@ import { format } from "date-fns";
import { Navigate } from "react-router-dom";
import { Pecha } from "@/components/ui/shadimport";
import { Button } from "@/components/ui/atoms/button";
import {
DialogDescription,
DialogFooter,
} from "@/components/ui/atoms/dialog";
import { DialogDescription, DialogFooter } from "@/components/ui/atoms/dialog";
import { Pagination } from "@/components/ui/molecules/pagination/Pagination";
import { getApiErrorMessage } from "@/lib/apiErrors";
import { useUserInfo } from "@/hooks/useUserInfo";
import {
canAccessAdminAuthors,
isSuperAdmin,
} from "@/lib/platformAccess";
import { canAccessAdminAuthors, isSuperAdmin } from "@/lib/platformAccess";
import { ROUTES } from "@/routes/paths";
import {
createChinaRestrictedItem,
Expand Down
4 changes: 1 addition & 3 deletions src/components/routes/dashboard/dashboardTableUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,5 @@ export function statusChip(status: string) {
}

export function languageChip(code: DashboardLanguageCode) {
return (
<span className={LANGUAGE_CHIP_CLASS}>{languageChipLabel(code)}</span>
);
return <span className={LANGUAGE_CHIP_CLASS}>{languageChipLabel(code)}</span>;
}
8 changes: 2 additions & 6 deletions src/components/routes/groups/GroupAboutEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,8 @@ type AssociationBaselines = {
};

const GroupAboutEditPage = () => {
const {
group,
groupId,
myRole,
readOnlyPlatform,
} = useOutletContext<GroupOutletContext>();
const { group, groupId, myRole, readOnlyPlatform } =
useOutletContext<GroupOutletContext>();
const queryClient = useQueryClient();
const hydratedRef = useRef<string | null>(null);

Expand Down
5 changes: 1 addition & 4 deletions src/components/routes/groups/GroupAboutPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import { Pecha } from "@/components/ui/shadimport";
import { Button } from "@/components/ui/atoms/button";
import type { LanguageCode } from "@/schema/SeriesSchema";
import { ROUTES } from "@/routes/paths";
import {
languageLabelForCode,
resolveGroupBannerUrl,
} from "./api/groupsApi";
import { languageLabelForCode, resolveGroupBannerUrl } from "./api/groupsApi";
import { canEditGroupSettings } from "./lib/groupPermissions";
import { GroupDetailCard } from "./components/GroupSection";
import type { GroupOutletContext } from "./GroupLayout";
Expand Down
5 changes: 4 additions & 1 deletion src/components/routes/groups/GroupLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ const GroupLayout = () => {
</div>
</GroupPageShell>

<Pecha.AlertDialog open={deleteOpen} onOpenChange={handleDeleteOpenChange}>
<Pecha.AlertDialog
open={deleteOpen}
onOpenChange={handleDeleteOpenChange}
>
<Pecha.AlertDialogContent>
<Pecha.AlertDialogHeader>
<Pecha.AlertDialogTitle>Delete group?</Pecha.AlertDialogTitle>
Expand Down
4 changes: 1 addition & 3 deletions src/components/routes/groups/GroupMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import type { GroupOutletContext } from "./GroupLayout";
const GroupMembersPage = () => {
const { group, groupId } = useOutletContext<GroupOutletContext>();

return (
<GroupMembersPanel groupId={groupId} members={group.members ?? []} />
);
return <GroupMembersPanel groupId={groupId} members={group.members ?? []} />;
};

export default GroupMembersPage;
6 changes: 5 additions & 1 deletion src/components/routes/groups/GroupsList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { useNavigate } from "react-router-dom";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/atoms/avatar";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/atoms/avatar";
import { ROUTES } from "@/routes/paths";
import {
groupTypeLabel,
Expand Down
41 changes: 28 additions & 13 deletions src/components/routes/groups/components/GroupAccumulatorsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,9 @@ const GroupAccumulatorsPanel = ({
const platformReadOnly = isReviewer(userInfo?.platform_role);
const canCreate =
!platformReadOnly && groupRole != null && groupRole !== "VIEWER";
const canManage = !platformReadOnly && canChangeContentStatus(
groupRole,
userInfo?.platform_role,
);
const canManage =
!platformReadOnly &&
canChangeContentStatus(groupRole, userInfo?.platform_role);

const [dialogOpen, setDialogOpen] = useState(false);
const [editing, setEditing] = useState<GroupAccumulatorDTO | null>(null);
Expand Down Expand Up @@ -122,7 +121,9 @@ const GroupAccumulatorsPanel = ({
let cancelled = false;
searchAccumulatorPresets({ limit: 100 }).then((result) => {
if (cancelled) return;
const match = result.items.find((preset) => preset.id === form.preset?.id);
const match = result.items.find(
(preset) => preset.id === form.preset?.id,
);
if (match) {
setForm((prev) => ({ ...prev, preset: match }));
}
Expand Down Expand Up @@ -318,8 +319,12 @@ const GroupAccumulatorsPanel = ({
? accumulator.target_count.toLocaleString()
: "—"}
</span>
<span>Start: {formatAccumulatorDate(accumulator.start_date)}</span>
<span>End: {formatAccumulatorDate(accumulator.end_date)}</span>
<span>
Start: {formatAccumulatorDate(accumulator.start_date)}
</span>
<span>
End: {formatAccumulatorDate(accumulator.end_date)}
</span>
<span>
Joined:{" "}
{accumulator.member_count != null
Expand Down Expand Up @@ -401,7 +406,10 @@ const GroupAccumulatorsPanel = ({
<div className="grid sm:grid-cols-2 gap-4">
<div className="space-y-2">
<p className="text-sm font-bold">Start date</p>
<Pecha.Popover open={startDateOpen} onOpenChange={setStartDateOpen}>
<Pecha.Popover
open={startDateOpen}
onOpenChange={setStartDateOpen}
>
<Pecha.PopoverTrigger asChild>
<Button
type="button"
Expand Down Expand Up @@ -469,7 +477,9 @@ const GroupAccumulatorsPanel = ({
<Pecha.Calendar
mode="single"
selected={
form.end_date ? fromBackendISO(form.end_date) : undefined
form.end_date
? fromBackendISO(form.end_date)
: undefined
}
onSelect={(d) => {
setEndDateOpen(false);
Expand Down Expand Up @@ -501,14 +511,19 @@ const GroupAccumulatorsPanel = ({
>
<span
className={
form.preset ? "text-foreground" : "text-muted-foreground"
form.preset
? "text-foreground"
: "text-muted-foreground"
}
>
{presetTriggerLabel}
</span>
</Button>
</Pecha.PopoverTrigger>
<Pecha.PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start">
<Pecha.PopoverContent
className="w-[--radix-popover-trigger-width] p-0"
align="start"
>
<Pecha.Command shouldFilter={false}>
<Pecha.CommandInput
placeholder="Search presets…"
Expand Down Expand Up @@ -601,8 +616,8 @@ const GroupAccumulatorsPanel = ({
<Pecha.AlertDialogTitle>Delete accumulator?</Pecha.AlertDialogTitle>
<Pecha.AlertDialogDescription>
This will remove &ldquo;
{deleteTarget?.title?.trim() || "Untitled accumulator"}&rdquo; from
the group. This action cannot be undone.
{deleteTarget?.title?.trim() || "Untitled accumulator"}&rdquo;
from the group. This action cannot be undone.
</Pecha.AlertDialogDescription>
</Pecha.AlertDialogHeader>
<Pecha.AlertDialogFooter>
Expand Down
11 changes: 6 additions & 5 deletions src/components/routes/groups/components/GroupTitleWithAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/atoms/avatar";
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/atoms/avatar";
import { cn } from "@/lib/utils";

type GroupTitleWithAvatarProps = {
Expand All @@ -23,12 +27,9 @@ const GroupTitleWithAvatar = ({
}: GroupTitleWithAvatarProps) => {
return (
<div className={cn("flex items-center gap-3 min-w-0", className)}>

<Avatar className={cn(sizeClasses[size], "shrink-0")}>
<AvatarImage src={avatarUrl ?? undefined} />
<AvatarFallback>
{title.charAt(0)}
</AvatarFallback>
<AvatarFallback>{title.charAt(0)}</AvatarFallback>
</Avatar>
<span className={cn("truncate", titleClassName)}>{title}</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,10 @@ describe("SeriesDetailsPage", () => {

it("updates URL params when selecting a language tab", async () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});

render(
Expand Down
7 changes: 6 additions & 1 deletion src/components/routes/series-details/SeriesDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { Link, useNavigate, useParams, useSearchParams } from "react-router-dom";
import {
Link,
useNavigate,
useParams,
useSearchParams,
} from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { IoMdArrowBack } from "react-icons/io";
import { toast } from "sonner";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export function parseSeriesLanguageParam(
searchParams: URLSearchParams,
): LanguageCode {
return (
normalizeLanguageCode(searchParams.get("language") ?? "") ?? DEFAULT_LANGUAGE
normalizeLanguageCode(searchParams.get("language") ?? "") ??
DEFAULT_LANGUAGE
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/routes/tags/TagsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const TagsTable = ({
)}
</div>
</Pecha.TableCell>

<Pecha.TableCell>{tag.plan_ids.length}</Pecha.TableCell>
{showActionsColumn ? (
<Pecha.TableCell>
Expand Down
40 changes: 28 additions & 12 deletions src/components/routes/verse-of-day/VerseOfDay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getApiErrorMessage } from "@/lib/apiErrors";
import {
fetchVerseOfDayList,
deleteVerseOfDay,
type SortOrder,
type VerseOfDayItem,
} from "./api/verseOfDayApi";
import VerseOfDayList from "./VerseOfDayList";
Expand All @@ -21,6 +22,7 @@ const PAGE_SIZE = 10;
const VerseOfDay = () => {
const [search, setSearch] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
const [debouncedSearch] = useDebounce(search, 500);
const [formOpen, setFormOpen] = useState(false);
const [editingItem, setEditingItem] = useState<VerseOfDayItem | null>(null);
Expand All @@ -29,8 +31,14 @@ const VerseOfDay = () => {
const queryClient = useQueryClient();

const { data, isLoading, error } = useQuery({
queryKey: ["verse-of-day-list", currentPage, debouncedSearch],
queryFn: fetchVerseOfDayList,
queryKey: ["verse-of-day-list", currentPage, debouncedSearch, sortOrder],
queryFn: () =>
fetchVerseOfDayList({
page: currentPage,
limit: PAGE_SIZE,
search: debouncedSearch,
sortOrder,
}),
refetchOnWindowFocus: false,
retry: false,
});
Expand All @@ -44,6 +52,9 @@ const VerseOfDay = () => {
onSuccess: () => {
toast.success("Verse of Day deleted successfully!");
setDeleteTarget(null);
if (data?.verses.length === 1 && currentPage > 1) {
setCurrentPage(currentPage - 1);
}
invalidateVerses();
},
onError: (err) => {
Expand All @@ -68,14 +79,15 @@ const VerseOfDay = () => {
invalidateVerses();
};

const sortedVerses = data?.verses
? [...data.verses].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)
: [];
const handleToggleSort = () => {
setSortOrder((prev) => (prev === "desc" ? "asc" : "desc"));
if (currentPage !== 1) setCurrentPage(1);
};

const verses = data?.verses ?? [];

const totalPages =
sortedVerses.length > 0 ? Math.ceil(sortedVerses.length / PAGE_SIZE) : 1;
const total = data?.total ?? 0;
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));

return (
<div className="flex flex-col h-screen overflow-hidden bg-[#F5F5F5] dark:bg-[#181818] font-dynamic">
Expand Down Expand Up @@ -109,7 +121,7 @@ const VerseOfDay = () => {
<p className="text-sm text-red-500 py-8">
Failed to load verses. {getApiErrorMessage(error)}
</p>
) : sortedVerses.length === 0 && !isLoading ? (
) : verses.length === 0 && !isLoading ? (
<div className="flex flex-col h-full items-center justify-center">
<p className="text-base text-muted-foreground">No verses found</p>
<Button
Expand All @@ -123,14 +135,18 @@ const VerseOfDay = () => {
) : (
<div className="h-full overflow-auto">
<VerseOfDayList
verses={verses}
isLoading={isLoading}
sortOrder={sortOrder}
onToggleSort={handleToggleSort}
onEdit={handleOpenEdit}
onDelete={setDeleteTarget}
/>
</div>
)}
</div>

{sortedVerses.length > 0 && (
{verses.length > 0 && (
<div className="border-t border-gray-200 dark:border-[#313132] px-6 py-4 bg-white dark:bg-[#1E1E1E]">
<Pagination
currentPage={currentPage}
Expand All @@ -148,7 +164,7 @@ const VerseOfDay = () => {
}}
editingItem={editingItem}
onSuccess={handleFormSuccess}
existingVerses={sortedVerses}
existingVerses={verses}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Duplicate Check Uses Current Page

verses now contains only the current server page and current search result, but it is passed as existingVerses for the form's duplicate-date check. When someone creates a verse from page 2 or from a filtered search, dates that already exist outside that subset are not checked, so the UI can submit a duplicate date that the old unpaginated list would have rejected.

/>

<Pecha.AlertDialog
Expand Down
Loading
Loading