Skip to content

Commit

Permalink
move where we have the delete button, and update the modal for confir…
Browse files Browse the repository at this point in the history
…ming deletion of resource to match the UI pattern we use for deleting offers
  • Loading branch information
tomas-salgado committed Nov 30, 2024
1 parent 5dde560 commit 48b3d84
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 120 deletions.
94 changes: 68 additions & 26 deletions apps/member-profile/app/routes/_profile.resources.$id_.delete.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
import { type ActionFunctionArgs, redirect } from '@remix-run/node';
import { Form, useNavigate, useSearchParams } from '@remix-run/react';
import { useState } from 'react';
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
redirect,
} from '@remix-run/node';
import {
Form as RemixForm,
useActionData,
useLoaderData,
useSearchParams,
} from '@remix-run/react';

import { Button, Modal } from '@oyster/ui';
import { db } from '@oyster/db';
import { Button, Form, getErrors, Modal } from '@oyster/ui';

import { deleteResource } from '@/modules/resource/use-cases/delete-resource';
import { Route } from '@/shared/constants';
import {
commitSession,
ensureUserAuthenticated,
toast,
user,
} from '@/shared/session.server';

export async function loader({ params, request }: LoaderFunctionArgs) {
const session = await ensureUserAuthenticated(request);

const resource = await db
.selectFrom('resources')
.select(['title'])
.where('id', '=', params.id as string)
.where('postedBy', '=', user(session))
.executeTakeFirst();

if (!resource) {
throw new Response(null, {
status: 404,
statusText: 'The resource you are looking for does not exist.',
});
}

return json(resource);
}

export async function action({ params, request }: ActionFunctionArgs) {
const session = await ensureUserAuthenticated(request);

const { id } = params;

if (!id) throw new Error('Resource ID is required');
Expand All @@ -17,15 +55,24 @@ export async function action({ params, request }: ActionFunctionArgs) {
const url = new URL(request.url);
const searchParams = url.searchParams.toString();

toast(session, {
message: 'Resource deleted successfully.',
});

return redirect(
`${Route['/resources']}${searchParams ? `?${searchParams}` : ''}`
`${Route['/resources']}${searchParams ? `?${searchParams}` : ''}`,
{
headers: {
'Set-Cookie': await commitSession(session),
},
}
);
}

export default function DeleteResource() {
const navigate = useNavigate();
export default function DeleteResourceModal() {
const { title } = useLoaderData<typeof loader>();
const [searchParams] = useSearchParams();
const [isDeleting, setIsDeleting] = useState(false);
const { error } = getErrors(useActionData<typeof action>());

const getReturnPath = () => {
const currentParams = searchParams.toString();
Expand All @@ -36,26 +83,21 @@ export default function DeleteResource() {
return (
<Modal onCloseTo={getReturnPath()}>
<Modal.Header>
<Modal.Title>
Are you sure you want to delete this resource?
</Modal.Title>
<Modal.Title>Delete Resource</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Description>This action cannot be undone.</Modal.Description>
<Form method="post" onSubmit={() => setIsDeleting(true)}>
<Button.Group>
<Button
type="button"
variant="secondary"
onClick={() => navigate(getReturnPath())}
disabled={isDeleting}
>
Cancel
</Button>
<Button type="submit" disabled={isDeleting}>
{isDeleting ? 'Deleting...' : 'Delete'}
</Button>

<Modal.Description>
Are you sure you want to delete "{title}"? This action cannot be undone.
</Modal.Description>

<RemixForm method="post">
<Form.ErrorMessage>{error}</Form.ErrorMessage>

<Button.Group flexDirection="row-reverse">
<Button.Submit color="error">Delete</Button.Submit>
</Button.Group>
</Form>
</RemixForm>
</Modal>
);
}
15 changes: 14 additions & 1 deletion apps/member-profile/app/routes/_profile.resources.$id_.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
redirect,
} from '@remix-run/node';
import {
generatePath,
Link,
Form as RemixForm,
useActionData,
useLoaderData,
Expand All @@ -21,6 +23,7 @@ import {
Button,
Divider,
Form,
getButtonCn,
getErrors,
Modal,
validateForm,
Expand Down Expand Up @@ -50,6 +53,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
'resources.link',
'resources.title',
'resources.type',
'resources.id',
],
where: { id: params.id as string },
});
Expand Down Expand Up @@ -170,8 +174,17 @@ export default function EditResourceModal() {

<Form.ErrorMessage>{error}</Form.ErrorMessage>

<Button.Group>
<Button.Group flexDirection="row-reverse" spacing="between">
<Button.Submit>Save</Button.Submit>

<Link
className={getButtonCn({ color: 'error', variant: 'secondary' })}
to={generatePath(Route['/resources/:id/delete'], {
id: resource.id,
})}
>
Delete
</Link>
</Button.Group>
</RemixForm>
</Modal>
Expand Down
118 changes: 25 additions & 93 deletions apps/member-profile/app/shared/components/resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,15 @@ import {
useFetcher,
useSearchParams,
} from '@remix-run/react';
import { type PropsWithChildren, useState } from 'react';
import {
ArrowUp,
BarChart2,
Edit,
MoreHorizontal,
Share,
Trash2,
} from 'react-feather';
import { type PropsWithChildren } from 'react';
import { ArrowUp, BarChart2, Edit, Share } from 'react-feather';
import { match } from 'ts-pattern';

import { ResourceType } from '@oyster/core/resources';
import {
cx,
Dropdown,
getIconButtonCn,
getTextCn,
IconButton,
Pill,
ProfilePicture,
Text,
Expand Down Expand Up @@ -293,7 +284,6 @@ function ResourceActionGroup({
shareableUri,
}: Pick<ResourceProps, 'editable' | 'id' | 'shareableUri'>) {
const [searchParams] = useSearchParams();
const [open, setOpen] = useState<boolean>(false);
const toast = useToast();
const { trackFromClient } = useMixpanelTracker();

Expand All @@ -302,10 +292,6 @@ function ResourceActionGroup({
backgroundColorOnHover: 'gray-200',
});

function onClick() {
setOpen(true);
}

return (
<ul className="flex items-center gap-1">
{!!editable && (
Expand All @@ -328,84 +314,30 @@ function ResourceActionGroup({
</Tooltip>
</li>
)}
{editable ? (
<li>
<Dropdown.Container onClose={() => setOpen(false)}>
<Tooltip>
<TooltipTrigger asChild>
<button
className={buttonClassName}
onClick={onClick}
type="button"
>
<MoreHorizontal />
</button>
</TooltipTrigger>
<TooltipContent>
<TooltipText>More Options</TooltipText>
</TooltipContent>
</Tooltip>

{open && (
<Dropdown>
<Dropdown.List>
<Dropdown.Item>
<button
onClick={() => {
navigator.clipboard.writeText(shareableUri);
toast({ message: 'Copied URL to clipboard!' });
trackFromClient({
event: 'Resource Link Copied',
properties: undefined,
});
}}
type="button"
>
<Share /> Copy Resource Link
</button>
</Dropdown.Item>
<Dropdown.Item>
<Link
to={{
pathname: generatePath(Route['/resources/:id/delete'], {
id,
}),
search: searchParams.toString(),
}}
>
<Trash2 /> Delete
</Link>
</Dropdown.Item>
</Dropdown.List>
</Dropdown>
)}
</Dropdown.Container>
</li>
) : (
<li>
<Tooltip>
<TooltipTrigger asChild>
<button
className={buttonClassName}
onClick={() => {
navigator.clipboard.writeText(shareableUri);
toast({ message: 'Copied URL to clipboard!' });
trackFromClient({
event: 'Resource Link Copied',
properties: undefined,
});
}}
type="button"
>
<Share />
</button>
</TooltipTrigger>
<TooltipContent>
<TooltipText>Copy Resource Link</TooltipText>
</TooltipContent>
</Tooltip>
</li>
)}
<li>
<Tooltip>
<TooltipTrigger asChild>
<button
className={buttonClassName}
onClick={() => {
navigator.clipboard.writeText(shareableUri);
toast({ message: 'Copied URL to clipboard!' });
trackFromClient({
event: 'Resource Link Copied',
properties: undefined,
});
}}
type="button"
>
<Share />
</button>
</TooltipTrigger>
<TooltipContent>
<TooltipText>Copy Resource Link</TooltipText>
</TooltipContent>
</Tooltip>
</li>
</ul>
);
}
Expand Down

0 comments on commit 48b3d84

Please sign in to comment.