Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/anon-company-rev…
Browse files Browse the repository at this point in the history
…iews
  • Loading branch information
ramiAbdou committed Aug 29, 2024
2 parents f6b8e70 + f80a185 commit e60cd7d
Show file tree
Hide file tree
Showing 96 changed files with 2,559 additions and 1,655 deletions.
4 changes: 0 additions & 4 deletions .husky/pre-commit

This file was deleted.

5 changes: 5 additions & 0 deletions CONTRIBUTORS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@
- Habeebah157
- bryanansong
- nathanallen242
- Soogz
- poughe
- katlj
- rafa1510
- Meron-b
94 changes: 94 additions & 0 deletions apps/admin-dashboard/app/routes/_dashboard.admins.$id.remove.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
redirect,
} from '@remix-run/node';
import {
Form as RemixForm,
useActionData,
useLoaderData,
} from '@remix-run/react';

import { getAdmin, removeAdmin } from '@oyster/core/admins';
import { Button, Form, Modal } from '@oyster/ui';

import { Route } from '@/shared/constants';
import {
commitSession,
ensureUserAuthenticated,
toast,
user,
} from '@/shared/session.server';

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

const admin = await getAdmin({
select: ['admins.firstName', 'admins.lastName'],
where: { id: params.id as string },
});

if (!admin) {
throw new Response(null, { status: 404 });
}

return json({
admin,
});
}

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

const result = await removeAdmin({
actor: user(session),
id: params.id as string,
});

if (!result.ok) {
return json({ error: result.error }, { status: result.code });
}

toast(session, {
message: 'Removed admin.',
});

return redirect(Route['/admins'], {
headers: {
'Set-Cookie': await commitSession(session),
},
});
}

export default function RemoveAdminPage() {
const { admin } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();

return (
<Modal onCloseTo={Route['/admins']}>
<Modal.Header>
<Modal.Title>
Remove {admin.firstName} {admin.lastName}
</Modal.Title>
<Modal.CloseButton />
</Modal.Header>

<Modal.Description>
This is not an undoable action. Are you sure want to remove this admin?
</Modal.Description>

<RemixForm className="form" method="post">
<Form.ErrorMessage>{actionData?.error}</Form.ErrorMessage>

<Button.Group>
<Button.Submit color="error">Remove</Button.Submit>
</Button.Group>
</RemixForm>
</Modal>
);
}

export function ErrorBoundary() {
return <></>;
}
57 changes: 46 additions & 11 deletions apps/admin-dashboard/app/routes/_dashboard.admins.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,58 @@
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { Outlet, useLoaderData } from '@remix-run/react';

import { listAdmins } from '@oyster/core/admins';
import {
doesAdminHavePermission,
getAdmin,
listAdmins,
} from '@oyster/core/admins';
import { type AdminRole } from '@oyster/core/admins.types';
import { AdminTable } from '@oyster/core/admins.ui';
import { Dashboard } from '@oyster/ui';

import { ensureUserAuthenticated } from '@/shared/session.server';
import { user } from '@/shared/session.server';

export async function loader({ request }: LoaderFunctionArgs) {
await ensureUserAuthenticated(request);

const admins = await listAdmins({
select: [
'admins.firstName',
'admins.lastName',
'admins.email',
'admins.id',
'admins.role',
],
const session = await ensureUserAuthenticated(request);

const [admin, _admins] = await Promise.all([
getAdmin({
select: ['admins.id', 'admins.role'],
where: { id: user(session) },
}),
listAdmins({
select: [
'admins.deletedAt',
'admins.firstName',
'admins.lastName',
'admins.email',
'admins.id',
'admins.role',
],
}),
]);

if (!admin) {
throw new Response(null, { status: 404 });
}

const admins = _admins.map(({ deletedAt, ...row }) => {
return {
...row,

// Admins can't delete themselves nor can they delete other admins with
// a higher role.
canRemove:
!deletedAt &&
row.id !== admin.id &&
doesAdminHavePermission({
minimumRole: row.role as AdminRole,
role: admin.role as AdminRole,
}),

isDeleted: !!deletedAt,
};
});

return json({
Expand Down
17 changes: 0 additions & 17 deletions apps/admin-dashboard/app/routes/_dashboard.bull.$queue._index.tsx

This file was deleted.

124 changes: 8 additions & 116 deletions apps/admin-dashboard/app/routes/_dashboard.bull.$queue.jobs.$id.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
} from '@remix-run/node';
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import {
generatePath,
Form as RemixForm,
type Params,
useLoaderData,
useParams,
} from '@remix-run/react';
import dayjs from 'dayjs';
import { type PropsWithChildren } from 'react';
import { ArrowUp, Copy, RefreshCw, Trash } from 'react-feather';
import { match } from 'ts-pattern';
import { z } from 'zod';

import { IconButton, Modal, Text } from '@oyster/ui';
import { Modal, Text } from '@oyster/ui';

import { QueueFromName } from '@/admin-dashboard.server';
import { BullQueue } from '@/admin-dashboard.ui';
import { validateQueue } from '@/shared/bull';
import { Route } from '@/shared/constants';
import { getTimezone } from '@/shared/cookies.server';
import { ensureUserAuthenticated } from '@/shared/session.server';

const BullParams = z.object({
queue: z.nativeEnum(BullQueue),
id: z.string(),
});

export async function loader({ params, request }: LoaderFunctionArgs) {
await ensureUserAuthenticated(request, {
minimumRole: 'owner',
Expand Down Expand Up @@ -84,60 +71,10 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
});
}

const JobAction = {
DUPLICATE: 'duplicate',
PROMOTE: 'promote',
REMOVE: 'remove',
RETRY: 'retry',
} as const;

export async function action({ params, request }: ActionFunctionArgs) {
await ensureUserAuthenticated(request, {
minimumRole: 'owner',
});

const form = await request.formData();

const result = z
.nativeEnum(JobAction)
.safeParse(Object.fromEntries(form).action);

if (!result.success) {
throw new Response(null, { status: 400 });
}

const job = await getJobFromParams(params);

await match(result.data)
.with('duplicate', async () => {
const queue = QueueFromName[job.queueName as BullQueue];

return queue.add(job.name, job.data);
})
.with('promote', async () => {
return job.promote();
})
.with('remove', async () => {
return job.remove();
})
.with('retry', async () => {
return job.retry();
})
.exhaustive();

return json({});
}

async function getJobFromParams(params: object) {
const result = BullParams.safeParse(params);
async function getJobFromParams(params: Params<string>) {
const queue = await validateQueue(params.queue);

if (!result.success) {
throw new Response(null, { status: 404 });
}

const queue = QueueFromName[result.data.queue];

const job = await queue.getJob(result.data.id);
const job = await queue.getJob(params.id as string);

if (!job) {
throw new Response(null, { status: 404 });
Expand All @@ -152,7 +89,7 @@ export default function JobPage() {

return (
<Modal
onCloseTo={generatePath(Route['/bull/:queue/jobs'], {
onCloseTo={generatePath(Route['/bull/:queue'], {
queue: queue as string,
})}
>
Expand All @@ -161,51 +98,6 @@ export default function JobPage() {
<Modal.CloseButton />
</Modal.Header>

<RemixForm className="ml-auto flex gap-2" method="post">
{general.state === 'delayed' && (
<IconButton
icon={<ArrowUp />}
backgroundColor="gray-100"
backgroundColorOnHover="gray-200"
name="action"
shape="square"
type="submit"
value={JobAction.PROMOTE}
/>
)}
{general.state === 'failed' && (
<IconButton
icon={<RefreshCw />}
backgroundColor="gray-100"
backgroundColorOnHover="gray-200"
name="action"
shape="square"
type="submit"
value={JobAction.RETRY}
/>
)}
{general.state === 'waiting' && (
<IconButton
icon={<Copy />}
backgroundColor="gray-100"
backgroundColorOnHover="gray-200"
name="action"
shape="square"
type="submit"
value={JobAction.DUPLICATE}
/>
)}
<IconButton
icon={<Trash className="text-red-600" />}
backgroundColor="gray-100"
backgroundColorOnHover="gray-200"
name="action"
shape="square"
type="submit"
value={JobAction.REMOVE}
/>
</RemixForm>

<JobSection>
<JobSectionTitle>General</JobSectionTitle>
<JobSectionData data={general} />
Expand Down
Loading

0 comments on commit e60cd7d

Please sign in to comment.