Skip to content

Commit

Permalink
Fix dialog action (#423)
Browse files Browse the repository at this point in the history
* Fix dialog action

* Allow dialog on multiple rows

* Apply prettier and add twMerge
  • Loading branch information
cregourd authored Sep 23, 2024
1 parent a20b10f commit f5347cf
Show file tree
Hide file tree
Showing 35 changed files with 337 additions and 190 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-worms-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Fix dialog action in form
5 changes: 5 additions & 0 deletions .changeset/good-vans-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@premieroctet/next-admin": patch
---

Add icons on action dropdown
2 changes: 1 addition & 1 deletion apps/docs/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function Hero() {
className="-top-40 left-0 md:-top-20 md:left-60"
fill="#82e9a6"
/>
<div className=" relative z-10 mx-auto w-full max-w-7xl p-4 pt-20 md:pt-0">
<div className="relative z-10 mx-auto w-full max-w-7xl p-4 pt-20 md:pt-0">
<h1 className="bg-opacity-50 bg-gradient-to-b from-black/70 to-black/90 bg-clip-text text-center text-4xl font-bold text-transparent dark:text-white md:text-7xl">
Full-featured Admin
<br /> for Next.js
Expand Down
8 changes: 3 additions & 5 deletions apps/docs/components/bento/Skeletons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const SkeletonOne = () => {
initial="initial"
animate="visible"
whileHover="hover"
className=" dark:bg-dot-white/[0.2] bg-dot-black/[0.2] flex h-full min-h-[6rem] w-full flex-1 flex-col space-y-2"
className="dark:bg-dot-white/[0.2] bg-dot-black/[0.2] flex h-full min-h-[6rem] w-full flex-1 flex-col space-y-2"
>
{false && (
<motion.div className="h-0 w-0 border-b-[50px] border-l-[30px] border-r-[30px] border-b-black border-l-transparent border-r-transparent" />
Expand Down Expand Up @@ -146,9 +146,7 @@ export const SkeletonThree = () => {
<CheckBadgeIcon className="absolute right-0 top-6 h-6 w-6 text-teal-600" />
</div>
</motion.div>
<motion.div className="pl-4 ">
{`includeRequiredFields = true`}
</motion.div>
<motion.div className="pl-4">{`includeRequiredFields = true`}</motion.div>
<motion.div>{`}`}</motion.div>
</motion.div>
);
Expand Down Expand Up @@ -188,7 +186,7 @@ export const SkeletonFour = () => {
>
<motion.div
variants={variants}
className="flex flex-row items-center space-x-2 rounded-full border border-neutral-100 bg-white p-2 dark:border-white/[0.4] dark:bg-black"
className="flex flex-row items-center space-x-2 rounded-full border border-neutral-100 bg-white p-2 dark:border-white/[0.4] dark:bg-black"
>
<MagnifyingGlassIcon className="h-6 w-6" />
<div className="h-4 w-full rounded-full bg-gray-100 dark:bg-neutral-700" />
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/effects/Spotlight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const Spotlight = ({ className, fill }: SpotlightProps) => {
return (
<svg
className={cn(
"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] opacity-0 lg:w-[84%]",
"animate-spotlight pointer-events-none absolute z-[1] h-[169%] w-[138%] opacity-0 lg:w-[84%]",
className
)}
xmlns="http://www.w3.org/2000/svg"
Expand Down
7 changes: 2 additions & 5 deletions apps/example/components/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@ const DatePicker = ({ value, name, onChange, disabled, required }: Props) => {
wrapperClassName="w-full"
disabled={disabled}
required={required}
className="
dark:bg-dark-nextadmin-background-subtle dark:ring-dark-nextadmin-border-strong
text-nextadmin-content-inverted
dark:text-dark-nextadmin-content-inverted ring-nextadmin-border-default focus:ring-nextadmin-brand-default dark:focus:ring-dark-nextadmin-brand-default block w-full rounded-md border-0 px-2 py-1.5 text-sm shadow-sm ring-1 ring-1 ring-inset ring-inset transition-all duration-300 placeholder:text-gray-400 focus:ring-1 focus:ring-2 focus:ring-inset focus:ring-inset focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:leading-6 [&>div]:border-none"
className="dark:bg-dark-nextadmin-background-subtle dark:ring-dark-nextadmin-border-strong text-nextadmin-content-inverted dark:text-dark-nextadmin-content-inverted ring-nextadmin-border-default focus:ring-nextadmin-brand-default dark:focus:ring-dark-nextadmin-brand-default block w-full rounded-md border-0 px-2 py-1.5 text-sm shadow-sm ring-1 ring-inset transition-all duration-300 placeholder:text-gray-400 focus:ring-1 focus:ring-2 focus:ring-inset focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:leading-6 [&>div]:border-none"
/>
<input type="hidden" name={name} value={value ?? ""} step="0.001"/>
<input type="hidden" name={name} value={value ?? ""} step="0.001" />
</>
);
};
Expand Down
26 changes: 14 additions & 12 deletions apps/example/components/UserDetailsDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ type Props = ClientActionDialogContentProps<"User">;

const UserDetailsDialog = ({ data, onClose }: Props) => {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<h2 className="text-nextadmin-content-default dark:text-dark-nextadmin-content-default text-2xl font-semibold">
{data?.email.value as string}
</h2>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{data?.name.value as string}
</p>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{data?.role.value as string}
</p>
</div>
<div className="flex flex-col gap-8">
{data?.map((user) => (
<div className="flex flex-col gap-2" key={user.id}>
<h2 className="text-nextadmin-content-default dark:text-dark-nextadmin-content-default text-2xl font-semibold">
{user.email as string}
</h2>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{user.name as string}
</p>
<p className="text-nextadmin-content-subtle dark:text-dark-nextadmin-content-subtle">
{user.role as string}
</p>
</div>
))}
<div className="flex">
<Button variant="default" onClick={onClose}>
Close
Expand Down
2 changes: 2 additions & 0 deletions apps/example/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export const options: NextAdminOptions = {
actions: [
{
id: "submit-email",
icon: "EnvelopeIcon",
title: "actions.user.email.title",
action: async (ids) => {
console.log("Sending email to " + ids.length + " users");
Expand All @@ -165,6 +166,7 @@ export const options: NextAdminOptions = {
},
{
type: "dialog",
icon: "EyeIcon",
id: "user-details",
title: "actions.user.details.title",
component: <UserDetailsDialog />,
Expand Down
8 changes: 8 additions & 0 deletions apps/example/pageRouterOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextAdminOptions } from "@premieroctet/next-admin";
import DatePicker from "./components/DatePicker";
import PasswordInput from "./components/PasswordInput";
import UserDetailsDialog from "./components/UserDetailsDialogContent";

export const options: NextAdminOptions = {
title: "⚡️ My Admin Page Router",
Expand Down Expand Up @@ -116,6 +117,13 @@ export const options: NextAdminOptions = {
successMessage: "Email sent successfully",
errorMessage: "Error while sending email",
},
{
type: "dialog",
icon: "EyeIcon",
id: "user-details",
title: "User details",
component: <UserDetailsDialog />,
},
],
},
Post: {
Expand Down
3 changes: 1 addition & 2 deletions packages/next-admin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Bonus: Customize the admin dashboard by passing the `NextAdminOptions` options t

An example of `next-admin` options:

```tsx
```tsx
// app/admin/options.ts
import { NextAdminOptions } from "@premieroctet/next-admin";

Expand Down Expand Up @@ -119,7 +119,6 @@ export const options: NextAdminOptions = {
};
```


## 📄 Documentation

For detailed documentation, please refer to the [documentation](https://next-admin-docs.vercel.app/).
Expand Down
26 changes: 25 additions & 1 deletion packages/next-admin/src/appHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createEdgeRouter } from "next-connect";
import { NextRequest, NextResponse } from "next/server";
import { HookError } from "./exceptions/HookError";
import { handleOptionsSearch } from "./handlers/options";
import { deleteResource, submitResource } from "./handlers/resources";
import {
Expand All @@ -9,13 +10,13 @@ import {
ServerAction,
} from "./types";
import { hasPermission } from "./utils/permissions";
import { getRawData } from "./utils/prisma";
import {
formatId,
getFormValuesFromFormData,
getResourceFromParams,
getResources,
} from "./utils/server";
import { HookError } from "./exceptions/HookError";

export const createHandler = <P extends string = "nextadmin">({
apiBasePath,
Expand All @@ -41,6 +42,29 @@ export const createHandler = <P extends string = "nextadmin">({
}

router
.get(`${apiBasePath}/:model/raw`, async (req, ctx) => {
const resource = getResourceFromParams(ctx.params[paramKey], resources);

if (!resource) {
return NextResponse.json(
{ error: "Resource not found" },
{ status: 404 }
);
}

const ids = req.nextUrl.searchParams
.get("ids")
?.split(",")
.map((id) => formatId(resource, id));

if (!ids) {
return NextResponse.json({ error: "No ids provided" }, { status: 400 });
}

const data = await getRawData({ prisma, resource, resourceIds: ids });

return NextResponse.json(data);
})
.post(`${apiBasePath}/:model/actions/:id`, async (req, ctx) => {
const id = ctx.params[paramKey].at(-1)!;

Expand Down
13 changes: 10 additions & 3 deletions packages/next-admin/src/appRouter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { GetMainLayoutPropsParams, GetNextAdminPropsParams } from "./types";
import { getMainLayoutProps as _getMainLayoutProps, getPropsFromParams as _getPropsFromParams } from "./utils/props";
import {
getMainLayoutProps as _getMainLayoutProps,
getPropsFromParams as _getPropsFromParams,
} from "./utils/props";

export const getNextAdminProps = async (params: Omit<GetNextAdminPropsParams, 'isAppDir'>) => {
export const getNextAdminProps = async (
params: Omit<GetNextAdminPropsParams, "isAppDir">
) => {
"use server";
return _getPropsFromParams({ ...params, isAppDir: true });
};

export const getMainLayoutProps = (args: Omit<GetMainLayoutPropsParams, 'isAppDir' | 'params'>) => _getMainLayoutProps({ ...args, isAppDir: true });
export const getMainLayoutProps = (
args: Omit<GetMainLayoutPropsParams, "isAppDir" | "params">
) => _getMainLayoutProps({ ...args, isAppDir: true });
31 changes: 18 additions & 13 deletions packages/next-admin/src/components/ActionDropdownItem.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,50 @@
import * as OutlineIcons from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useClientDialog } from "../context/ClientDialogContext";
import { useClientActionDialog } from "../context/ClientActionDialogContext";
import { useI18n } from "../context/I18nContext";
import { useAction } from "../hooks/useAction";
import { ModelAction, ModelName } from "../types";
import { DropdownItem } from "./radix/Dropdown";
import { twMerge } from "tailwind-merge";

type Props = {
action: ModelAction<ModelName> | Omit<ModelAction<ModelName>, "action">;
resource: ModelName;
resourceIds: string[] | number[];
data?: any;
};

const ActionDropdownItem = ({ action, resource, resourceIds, data }: Props) => {
const ActionDropdownItem = ({ action, resource, resourceIds }: Props) => {
const { t } = useI18n();
const { runAction } = useAction(resource, resourceIds);
const { open: openActionDialog } = useClientActionDialog();
const isClientAction =
"component" in action && "type" in action && action.type === "dialog";
const { open } = useClientDialog();

const Icon = action.icon && OutlineIcons[action.icon];

return (
<DropdownItem
key={action.title}
className={clsx("cursor-pointer rounded-md px-2 py-1", {
"text-red-700 dark:text-red-400": action.style === "destructive",
"hover:bg-red-50": action.style === "destructive",
})}
className={twMerge(
clsx("flex cursor-pointer items-center gap-2 rounded-md px-2 py-1", {
"text-red-700 dark:text-red-400": action.style === "destructive",
"hover:bg-red-50": action.style === "destructive",
})
)}
onClick={(evt) => {
evt.stopPropagation();
if (isClientAction) {
open({
actionId: action.id,
data: data,
resource: resource,
resourceId: resourceIds[0],
openActionDialog({
action: action,
resource,
resourceIds,
});
} else {
runAction(action);
}
}}
>
{Icon && <Icon className="h-5 w-5" />}
{t(action.title)}
</DropdownItem>
);
Expand Down
5 changes: 2 additions & 3 deletions packages/next-admin/src/components/Breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,11 @@ export default function Breadcrumb({
page.current
? "text-nextadmin-brand-subtle dark:text-dark-nextadmin-brand-subtle"
: "text-nextadmin-menu-default dark:text-dark-nextadmin-menu-color"
}
`}
} `}
aria-current={page.current ? "page" : undefined}
>
{!!page.icon && (
<ResourceIcon icon={page.icon} className="h-5 w-5 " />
<ResourceIcon icon={page.icon} className="h-5 w-5" />
)}
<span className="flex-basis-0 flex-1 overflow-hidden text-ellipsis whitespace-nowrap">
{page.label}
Expand Down
Loading

0 comments on commit f5347cf

Please sign in to comment.