mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-06 09:59:00 +02:00
♻️ Rename space folder (#1666)
This commit is contained in:
parent
aa721d9369
commit
5f76285f10
48 changed files with 58 additions and 448 deletions
|
@ -1,130 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { createContext, useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useRequiredContext } from "@/components/use-required-context";
|
||||
|
||||
import { PollSelectionActionBar } from "./poll-selection-action-bar";
|
||||
|
||||
type RowSelectionState = Record<string, boolean>;
|
||||
|
||||
type PollSelectionContextType = {
|
||||
selectedPolls: RowSelectionState;
|
||||
setSelectedPolls: (selection: RowSelectionState) => void;
|
||||
selectPolls: (pollIds: string[]) => void;
|
||||
unselectPolls: (pollIds: string[]) => void;
|
||||
togglePollSelection: (pollId: string) => void;
|
||||
clearSelection: () => void;
|
||||
isSelected: (pollId: string) => boolean;
|
||||
getSelectedPollIds: () => string[];
|
||||
selectedCount: number;
|
||||
};
|
||||
|
||||
const PollSelectionContext = createContext<PollSelectionContextType | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
type PollSelectionProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const PollSelectionProvider = ({
|
||||
children,
|
||||
}: PollSelectionProviderProps) => {
|
||||
const [selectedPolls, setSelectedPolls] = useState<RowSelectionState>({});
|
||||
|
||||
const selectPolls = useCallback((pollIds: string[]) => {
|
||||
setSelectedPolls((prev) => {
|
||||
const newSelection = { ...prev };
|
||||
pollIds.forEach((id) => {
|
||||
newSelection[id] = true;
|
||||
});
|
||||
return newSelection;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const unselectPolls = useCallback(
|
||||
(pollIds: string[]) =>
|
||||
setSelectedPolls((prev) => {
|
||||
const newSelection = { ...prev };
|
||||
pollIds.forEach((id) => {
|
||||
delete newSelection[id];
|
||||
});
|
||||
return newSelection;
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const togglePollSelection = useCallback(
|
||||
(pollId: string) =>
|
||||
setSelectedPolls((prev) => {
|
||||
const newSelection = { ...prev };
|
||||
if (newSelection[pollId]) {
|
||||
delete newSelection[pollId];
|
||||
} else {
|
||||
newSelection[pollId] = true;
|
||||
}
|
||||
return newSelection;
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const clearSelection = useCallback(() => setSelectedPolls({}), []);
|
||||
|
||||
const isSelected = useCallback(
|
||||
(pollId: string) => Boolean(selectedPolls[pollId]),
|
||||
[selectedPolls],
|
||||
);
|
||||
|
||||
const getSelectedPollIds = useCallback(
|
||||
() => Object.keys(selectedPolls),
|
||||
[selectedPolls],
|
||||
);
|
||||
|
||||
const selectedCount = useMemo(
|
||||
() => Object.keys(selectedPolls).length,
|
||||
[selectedPolls],
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
selectedPolls,
|
||||
setSelectedPolls,
|
||||
selectPolls,
|
||||
unselectPolls,
|
||||
togglePollSelection,
|
||||
clearSelection,
|
||||
isSelected,
|
||||
getSelectedPollIds,
|
||||
selectedCount,
|
||||
}),
|
||||
[
|
||||
selectedPolls,
|
||||
setSelectedPolls,
|
||||
selectPolls,
|
||||
unselectPolls,
|
||||
togglePollSelection,
|
||||
clearSelection,
|
||||
isSelected,
|
||||
getSelectedPollIds,
|
||||
selectedCount,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<PollSelectionContext.Provider value={value}>
|
||||
{children}
|
||||
<PollSelectionActionBar />
|
||||
</PollSelectionContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const usePollSelection = () => {
|
||||
const context = useRequiredContext(
|
||||
PollSelectionContext,
|
||||
"usePollSelection must be used within a PollSelectionProvider",
|
||||
);
|
||||
|
||||
return context;
|
||||
};
|
|
@ -1,142 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
ActionBarContainer,
|
||||
ActionBarContent,
|
||||
ActionBarGroup,
|
||||
ActionBarPortal,
|
||||
} from "@rallly/ui/action-bar";
|
||||
import { Button } from "@rallly/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@rallly/ui/dialog";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { TrashIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { deletePolls } from "@/app/[locale]/(admin)/polls/actions";
|
||||
import { Trans } from "@/components/trans";
|
||||
|
||||
import { usePollSelection } from "./context";
|
||||
|
||||
const MActionBar = motion(ActionBarContainer);
|
||||
|
||||
export function PollSelectionActionBar() {
|
||||
const { selectedCount, clearSelection, getSelectedPollIds } =
|
||||
usePollSelection();
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false);
|
||||
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||
|
||||
const handleDelete = async () => {
|
||||
const selectedPollIds = getSelectedPollIds();
|
||||
if (selectedPollIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
const result = await deletePolls(selectedPollIds);
|
||||
|
||||
if (result.success) {
|
||||
setIsDeleteDialogOpen(false);
|
||||
clearSelection();
|
||||
} else {
|
||||
// Handle error case
|
||||
console.error("Failed to delete polls:", result.error);
|
||||
}
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionBarPortal>
|
||||
<AnimatePresence>
|
||||
{selectedCount > 0 && (
|
||||
<MActionBar
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 20 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 500,
|
||||
damping: 30,
|
||||
mass: 0.5,
|
||||
}}
|
||||
>
|
||||
<ActionBarContent>
|
||||
<span className="text-sm font-medium">
|
||||
<Trans
|
||||
i18nKey="selectedPolls"
|
||||
defaults="{count} {count, plural, one {poll} other {polls}} selected"
|
||||
values={{ count: selectedCount }}
|
||||
/>
|
||||
</span>
|
||||
</ActionBarContent>
|
||||
<ActionBarGroup>
|
||||
<Button
|
||||
variant="actionBar"
|
||||
onClick={clearSelection}
|
||||
className="text-action-bar-foreground"
|
||||
>
|
||||
<Trans i18nKey="unselectAll" defaults="Unselect All" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setIsDeleteDialogOpen(true)}
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
<Trans i18nKey="delete" defaults="Delete" />
|
||||
</Button>
|
||||
</ActionBarGroup>
|
||||
</MActionBar>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Delete Polls Dialog */}
|
||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<DialogContent size="sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans i18nKey="deletePolls" defaults="Delete Polls" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm">
|
||||
{selectedCount === 1 ? (
|
||||
<Trans
|
||||
i18nKey="deletePollDescription"
|
||||
defaults="Are you sure you want to delete this poll? This action cannot be undone."
|
||||
/>
|
||||
) : (
|
||||
<Trans
|
||||
i18nKey="deletePollsDescription"
|
||||
defaults="Are you sure you want to delete these {count} polls? This action cannot be undone."
|
||||
values={{ count: selectedCount }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsDeleteDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="cancel" defaults="Cancel" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDelete}
|
||||
loading={isDeleting}
|
||||
>
|
||||
<Trans i18nKey="delete" defaults="Delete" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</ActionBarPortal>
|
||||
);
|
||||
}
|
|
@ -4,7 +4,7 @@ import { CheckIcon, PlusIcon, ZapIcon } from "lucide-react";
|
|||
import Link from "next/link";
|
||||
import { Trans } from "react-i18next/TransWithoutContext";
|
||||
|
||||
import { GroupPollIcon } from "@/app/[locale]/(admin)/app-card";
|
||||
import { PollPageIcon } from "@/app/components/page-icons";
|
||||
import { getGuestPolls } from "@/features/quick-create/lib/get-guest-polls";
|
||||
import { getTranslation } from "@/i18n/server";
|
||||
|
||||
|
@ -50,10 +50,10 @@ export async function QuickCreateWidget() {
|
|||
<li key={poll.id}>
|
||||
<Link
|
||||
href={`/poll/${poll.id}`}
|
||||
className="flex items-center gap-3 rounded-xl border bg-white p-3 hover:bg-gray-50 active:bg-gray-100"
|
||||
className="flex items-center gap-3 rounded-2xl border bg-white p-3 hover:bg-gray-50 active:bg-gray-100"
|
||||
>
|
||||
<div>
|
||||
<GroupPollIcon size="lg" />
|
||||
<PollPageIcon size="xl" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-medium">{poll.title}</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue