mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on project.
This commit is contained in:
@@ -11,7 +11,6 @@ import {prisma} from "@/prisma";
|
|||||||
export default async function Layout({children}: { children: React.ReactNode }) {
|
export default async function Layout({children}: { children: React.ReactNode }) {
|
||||||
|
|
||||||
const user = await currentUser()
|
const user = await currentUser()
|
||||||
console.log(user)
|
|
||||||
if (user) {
|
if (user) {
|
||||||
const userInfo = await prisma.user.findUnique({
|
const userInfo = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
|||||||
import {requiredCurrentUser} from "@/auth/current-user";
|
import {requiredCurrentUser} from "@/auth/current-user";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {ProjectForm} from "@/components/wrappers/project/ProjectForm";
|
import {ProjectForm} from "@/components/wrappers/Project/ProjectForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{
|
export default async function RoutePage(props: PageParams<{
|
||||||
@@ -16,12 +16,31 @@ export default async function RoutePage(props: PageParams<{
|
|||||||
const project = await prisma.project.findUnique({
|
const project = await prisma.project.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: projectId,
|
id: projectId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
databases: {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!project) {
|
if (!project) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
const organization = await prisma.organization.findFirst({
|
||||||
|
where: {
|
||||||
|
slug: 'default',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const availableDatabases = await prisma.database.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ projectId: null },
|
||||||
|
{ projectId: project.id },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +51,7 @@ export default async function RoutePage(props: PageParams<{
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<ProjectForm defaultValues={project} projectId={project.id}/>
|
<ProjectForm organization={organization} databases={availableDatabases} defaultValues={project} projectId={project.id}/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ export default async function RoutePage(props: PageParams<{ projectId: string }>
|
|||||||
|
|
||||||
const {projectId} = await props.params
|
const {projectId} = await props.params
|
||||||
|
|
||||||
// const project = await prisma.project.findUnique({
|
const project = await prisma.project.findUnique({
|
||||||
// where: {
|
where: {
|
||||||
// id: projectId,
|
id: projectId,
|
||||||
// },
|
},
|
||||||
// })
|
include:{
|
||||||
|
databases: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const project = projects.find(p => p.id === projectId)
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -35,7 +37,7 @@ export default async function RoutePage(props: PageParams<{ projectId: string }>
|
|||||||
|
|
||||||
</PageActions>
|
</PageActions>
|
||||||
</div>
|
</div>
|
||||||
{/*<PageDescription className="mt-5 sm:mt-0">{project.description}</PageDescription>*/}
|
{/*<PageDescription className="mt-5 sm:mt-0">{Project.description}</PageDescription>*/}
|
||||||
<PageContent className="flex flex-col w-full h-full">
|
<PageContent className="flex flex-col w-full h-full">
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {PageParams} from "@/types/next";
|
import {PageParams} from "@/types/next";
|
||||||
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
import {ProjectForm} from "@/components/wrappers/project/ProjectForm";
|
import {ProjectForm} from "@/components/wrappers/Project/ProjectForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
@@ -15,6 +15,12 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const organization = await prisma.organization.findFirst({
|
||||||
|
where: {
|
||||||
|
slug: 'default',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
@@ -23,12 +29,7 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
</PageTitle>
|
</PageTitle>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
Add databases
|
<ProjectForm databases={availableDatabases} organization={organization}/>
|
||||||
{availableDatabases.map(database =>
|
|
||||||
<div key={database.id}>
|
|
||||||
{database.name}
|
|
||||||
</div>)}
|
|
||||||
<ProjectForm/>
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,11 +5,21 @@ import {Button} from "@/components/ui/button";
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
|
||||||
import {projects} from "@/utils/mock-data";
|
import {projects} from "@/utils/mock-data";
|
||||||
import {ProjectCard} from "@/components/wrappers/project/ProjectCard";
|
import {ProjectCard} from "@/components/wrappers/Project/ProjectCard";
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
// const projects = await prisma.project.findMany({})
|
|
||||||
|
const projects = await prisma.project.findMany({
|
||||||
|
where: {
|
||||||
|
organization: {
|
||||||
|
slug: "default",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include:{
|
||||||
|
databases: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
@@ -44,8 +54,6 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
-9
@@ -1,9 +1,3 @@
|
|||||||
/*
|
|
||||||
Warnings:
|
|
||||||
|
|
||||||
- A unique constraint covering the columns `[generatedId]` on the table `databases` will be added. If there are existing duplicate values, this will fail.
|
|
||||||
|
|
||||||
*/
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "databases" ADD COLUMN "project_id" TEXT;
|
ALTER TABLE "databases" ADD COLUMN "project_id" TEXT;
|
||||||
|
|
||||||
@@ -34,9 +28,6 @@ CREATE UNIQUE INDEX "organizations_slug_key" ON "organizations"("slug");
|
|||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "projects_slug_key" ON "projects"("slug");
|
CREATE UNIQUE INDEX "projects_slug_key" ON "projects"("slug");
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "databases_generatedId_key" ON "databases"("generatedId");
|
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "projects" ADD CONSTRAINT "projects_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "projects" ADD CONSTRAINT "projects_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
+10
-12
@@ -6,6 +6,7 @@ import {env} from "@/env.mjs";
|
|||||||
import GoogleProvider from "next-auth/providers/google";
|
import GoogleProvider from "next-auth/providers/google";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
theme: {
|
theme: {
|
||||||
@@ -78,22 +79,20 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
// // session.user.authMethod = user.authMethod;
|
// // session.user.authMethod = user.authMethod;
|
||||||
// return session
|
// return session
|
||||||
// },
|
// },
|
||||||
async jwt({token, trigger, session, user}) {
|
async jwt({ token, trigger, session, user }) {
|
||||||
if (trigger === "update" && session) {
|
if (trigger === "update" && session) {
|
||||||
return {...session, user: {...token, ...session?.user}};
|
return { ...token, ...session?.user };
|
||||||
// return {...token, ...session?.user};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {...token, ...user};
|
return { ...token, ...user };
|
||||||
},
|
},
|
||||||
async session({session, token, user}) {
|
async session({ session, token, user }) {
|
||||||
session.user = token.user;
|
session.user = token;
|
||||||
// session.user = token;
|
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
async signIn({account, user, profile}) {
|
async signIn({ account, user, profile }) {
|
||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
where: {email: user.email},
|
where: { email: user.email },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
@@ -126,7 +125,7 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
|
|
||||||
// Update auth method if user exists
|
// Update auth method if user exists
|
||||||
await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {id: existingUser.id},
|
where: { id: existingUser.id },
|
||||||
data: {
|
data: {
|
||||||
authMethod: account.provider,
|
authMethod: account.provider,
|
||||||
},
|
},
|
||||||
@@ -136,5 +135,4 @@ export const {handlers, auth: baseAuth, signIn, signOut} = NextAuth({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3,7 +3,6 @@ import {User} from "@prisma/client";
|
|||||||
|
|
||||||
export const currentUser = async () => {
|
export const currentUser = async () => {
|
||||||
const session = await baseAuth();
|
const session = await baseAuth();
|
||||||
console.log("mysession", session)
|
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import {
|
|||||||
SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent,
|
SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent,
|
||||||
SidebarGroupLabel,
|
SidebarGroupLabel,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarMenu,
|
SidebarMenu, SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
|
import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
|
||||||
import {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
import {SidebarMenuCustom} from "@/components/wrappers/Dashboard/SideBar/SideBarMenu/SideBarMenu";
|
||||||
import {OrganizationComboBox} from "@/components/wrappers/organization/OrganizationCombobox";
|
import {OrganizationComboBox} from "@/components/wrappers/Organization/OrganizationCombobox";
|
||||||
import {prisma} from "@/prisma";
|
import {prisma} from "@/prisma";
|
||||||
|
|
||||||
export async function AppSidebar() {
|
export async function AppSidebar() {
|
||||||
|
|||||||
@@ -0,0 +1,382 @@
|
|||||||
|
//https://github.com/sersavan/shadcn-multi-select-component
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
XCircle,
|
||||||
|
ChevronDown,
|
||||||
|
XIcon,
|
||||||
|
WandSparkles,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variants for the multi-select component to handle different styles.
|
||||||
|
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
|
||||||
|
*/
|
||||||
|
const multiSelectVariants = cva(
|
||||||
|
"m-1 transition ease-in-out delay-150 hover:-translate-y-1 hover:scale-110 duration-300",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-foreground/10 text-foreground bg-card hover:bg-card/80",
|
||||||
|
secondary:
|
||||||
|
"border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||||
|
inverted: "inverted",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for MultiSelect component
|
||||||
|
*/
|
||||||
|
interface MultiSelectProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof multiSelectVariants> {
|
||||||
|
/**
|
||||||
|
* An array of option objects to be displayed in the multi-select component.
|
||||||
|
* Each option object has a label, value, and an optional icon.
|
||||||
|
*/
|
||||||
|
options: {
|
||||||
|
/** The text to display for the option. */
|
||||||
|
label: string;
|
||||||
|
/** The unique value associated with the option. */
|
||||||
|
value: string;
|
||||||
|
/** Optional icon component to display alongside the option. */
|
||||||
|
icon?: React.ComponentType<{ className?: string }>;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback function triggered when the selected values change.
|
||||||
|
* Receives an array of the new selected values.
|
||||||
|
*/
|
||||||
|
onValueChange: (value: string[]) => void;
|
||||||
|
|
||||||
|
/** The default selected values when the component mounts. */
|
||||||
|
defaultValue?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder text to be displayed when no values are selected.
|
||||||
|
* Optional, defaults to "Select options".
|
||||||
|
*/
|
||||||
|
placeholder?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation duration in seconds for the visual effects (e.g., bouncing badges).
|
||||||
|
* Optional, defaults to 0 (no animation).
|
||||||
|
*/
|
||||||
|
animation?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of items to display. Extra selected items will be summarized.
|
||||||
|
* Optional, defaults to 3.
|
||||||
|
*/
|
||||||
|
maxCount?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modality of the popover. When set to true, interaction with outside elements
|
||||||
|
* will be disabled and only popover content will be visible to screen readers.
|
||||||
|
* Optional, defaults to false.
|
||||||
|
*/
|
||||||
|
modalPopover?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, renders the multi-select component as a child of another component.
|
||||||
|
* Optional, defaults to false.
|
||||||
|
*/
|
||||||
|
asChild?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional class names to apply custom styles to the multi-select component.
|
||||||
|
* Optional, can be used to add custom styles.
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MultiSelect = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
MultiSelectProps
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
onValueChange,
|
||||||
|
variant,
|
||||||
|
defaultValue = [],
|
||||||
|
placeholder = "Select options",
|
||||||
|
animation = 0,
|
||||||
|
maxCount = 3,
|
||||||
|
modalPopover = false,
|
||||||
|
asChild = false,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const [selectedValues, setSelectedValues] =
|
||||||
|
React.useState<string[]>(defaultValue);
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||||
|
const [isAnimating, setIsAnimating] = React.useState(false);
|
||||||
|
|
||||||
|
const handleInputKeyDown = (
|
||||||
|
event: React.KeyboardEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
setIsPopoverOpen(true);
|
||||||
|
} else if (event.key === "Backspace" && !event.currentTarget.value) {
|
||||||
|
const newSelectedValues = [...selectedValues];
|
||||||
|
newSelectedValues.pop();
|
||||||
|
setSelectedValues(newSelectedValues);
|
||||||
|
onValueChange(newSelectedValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOption = (option: string) => {
|
||||||
|
const newSelectedValues = selectedValues.includes(option)
|
||||||
|
? selectedValues.filter((value) => value !== option)
|
||||||
|
: [...selectedValues, option];
|
||||||
|
setSelectedValues(newSelectedValues);
|
||||||
|
onValueChange(newSelectedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
setSelectedValues([]);
|
||||||
|
onValueChange([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTogglePopover = () => {
|
||||||
|
setIsPopoverOpen((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearExtraOptions = () => {
|
||||||
|
const newSelectedValues = selectedValues.slice(0, maxCount);
|
||||||
|
setSelectedValues(newSelectedValues);
|
||||||
|
onValueChange(newSelectedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleAll = () => {
|
||||||
|
if (selectedValues.length === options.length) {
|
||||||
|
handleClear();
|
||||||
|
} else {
|
||||||
|
const allValues = options.map((option) => option.value);
|
||||||
|
setSelectedValues(allValues);
|
||||||
|
onValueChange(allValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover
|
||||||
|
open={isPopoverOpen}
|
||||||
|
onOpenChange={setIsPopoverOpen}
|
||||||
|
modal={modalPopover}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
onClick={handleTogglePopover}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{selectedValues.length > 0 ? (
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
|
<div className="flex flex-wrap items-center">
|
||||||
|
{selectedValues.slice(0, maxCount).map((value) => {
|
||||||
|
const option = options.find((o) => o.value === value);
|
||||||
|
const IconComponent = option?.icon;
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
key={value}
|
||||||
|
className={cn(
|
||||||
|
isAnimating ? "animate-bounce" : "",
|
||||||
|
multiSelectVariants({ variant })
|
||||||
|
)}
|
||||||
|
style={{ animationDuration: `${animation}s` }}
|
||||||
|
>
|
||||||
|
{IconComponent && (
|
||||||
|
<IconComponent className="h-4 w-4 mr-2" />
|
||||||
|
)}
|
||||||
|
{option?.label}
|
||||||
|
<XCircle
|
||||||
|
className="ml-2 h-4 w-4 cursor-pointer"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
toggleOption(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{selectedValues.length > maxCount && (
|
||||||
|
<Badge
|
||||||
|
className={cn(
|
||||||
|
"bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
|
||||||
|
isAnimating ? "animate-bounce" : "",
|
||||||
|
multiSelectVariants({ variant })
|
||||||
|
)}
|
||||||
|
style={{ animationDuration: `${animation}s` }}
|
||||||
|
>
|
||||||
|
{`+ ${selectedValues.length - maxCount} more`}
|
||||||
|
<XCircle
|
||||||
|
className="ml-2 h-4 w-4 cursor-pointer"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
clearExtraOptions();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<XIcon
|
||||||
|
className="h-4 mx-2 cursor-pointer text-muted-foreground"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
handleClear();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="flex min-h-6 h-full"
|
||||||
|
/>
|
||||||
|
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-between w-full mx-auto">
|
||||||
|
<span className="text-sm text-muted-foreground mx-3">
|
||||||
|
{placeholder}
|
||||||
|
</span>
|
||||||
|
<ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-auto p-0"
|
||||||
|
align="start"
|
||||||
|
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search..."
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
key="all"
|
||||||
|
onSelect={toggleAll}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||||
|
selectedValues.length === options.length
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "opacity-50 [&_svg]:invisible"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<span>(Select All)</span>
|
||||||
|
</CommandItem>
|
||||||
|
{options.map((option) => {
|
||||||
|
const isSelected = selectedValues.includes(option.value);
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
onSelect={() => toggleOption(option.value)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||||
|
isSelected
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "opacity-50 [&_svg]:invisible"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
{option.icon && (
|
||||||
|
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
<span>{option.label}</span>
|
||||||
|
</CommandItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
{selectedValues.length > 0 && (
|
||||||
|
<>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={handleClear}
|
||||||
|
className="flex-1 justify-center cursor-pointer"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</CommandItem>
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="flex min-h-6 h-full"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => setIsPopoverOpen(false)}
|
||||||
|
className="flex-1 justify-center cursor-pointer max-w-full"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</CommandItem>
|
||||||
|
</div>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
{animation > 0 && selectedValues.length > 0 && (
|
||||||
|
<WandSparkles
|
||||||
|
className={cn(
|
||||||
|
"cursor-pointer my-2 text-foreground bg-background w-3 h-3",
|
||||||
|
isAnimating ? "" : "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
onClick={() => setIsAnimating(!isAnimating)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
MultiSelect.displayName = "MultiSelect";
|
||||||
+6
-1
@@ -8,6 +8,7 @@ import {Organization} from "@prisma/client";
|
|||||||
export type organizationComboBoxProps = {
|
export type organizationComboBoxProps = {
|
||||||
organizations: Organization[]
|
organizations: Organization[]
|
||||||
defaultOrganization: Organization
|
defaultOrganization: Organization
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -31,6 +32,10 @@ export function OrganizationComboBox(props: organizationComboBoxProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ComboBox values={values} defaultValue={defaultOrganization.id} onValueChange={updateSession}/>
|
<ComboBox
|
||||||
|
sideBar={true}
|
||||||
|
values={values}
|
||||||
|
defaultValue={defaultOrganization.id}
|
||||||
|
onValueChange={updateSession}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+2
@@ -11,12 +11,14 @@ export const ProjectCard = (props: projectCardProps) => {
|
|||||||
|
|
||||||
const {data: project} = props;
|
const {data: project} = props;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/dashboard/projects/${project.id}`}>
|
<Link href={`/dashboard/projects/${project.id}`}>
|
||||||
<Card className="flex flex-row justify-between">
|
<Card className="flex flex-row justify-between">
|
||||||
<div className="">
|
<div className="">
|
||||||
<CardHeader>{project.name}</CardHeader>
|
<CardHeader>{project.name}</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
{project.databases.length} databases
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"use server"
|
||||||
|
|
||||||
|
import {userAction} from "@/safe-actions";
|
||||||
|
import {prisma} from "@/prisma";
|
||||||
|
import {ProjectSchema} from "@/components/wrappers/Project/ProjectForm.schema";
|
||||||
|
import {z} from "zod";
|
||||||
|
import {ServerActionResult} from "@/types/action-type";
|
||||||
|
import {Projects} from "@prisma/client";
|
||||||
|
|
||||||
|
|
||||||
|
export const createProjectAction = userAction
|
||||||
|
.schema(
|
||||||
|
z.object({
|
||||||
|
data: ProjectSchema,
|
||||||
|
organizationId: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.action(async ({parsedInput, ctx}): Promise<ServerActionResult<Projects>> => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const project = await prisma.project.create({
|
||||||
|
data: {
|
||||||
|
name: parsedInput.data.name,
|
||||||
|
slug: parsedInput.data.slug,
|
||||||
|
organizationId: parsedInput.organizationId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const db of parsedInput.data.databases) {
|
||||||
|
|
||||||
|
await prisma.database.update({
|
||||||
|
where: {
|
||||||
|
id: db,
|
||||||
|
},
|
||||||
|
data:{
|
||||||
|
projectId: project.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: project,
|
||||||
|
actionSuccess: {
|
||||||
|
message: "Project has been successfully created.",
|
||||||
|
messageParams: {projectName: parsedInput.data.name},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Failed to create Project.",
|
||||||
|
status: 500, // Optional: Use a meaningful status code
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
messageParams: {projectName: parsedInput.data.name},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
+2
-1
@@ -1,9 +1,10 @@
|
|||||||
import {z} from "zod";
|
import {z} from "zod";
|
||||||
|
import Database from "@prisma/client"
|
||||||
|
|
||||||
export const ProjectSchema = z.object({
|
export const ProjectSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
|
databases: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ProjectSchema = z.infer<typeof ProjectSchema>;
|
export type ProjectSchema = z.infer<typeof ProjectSchema>;
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
useZodForm
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import {Input} from "@/components/ui/input";
|
||||||
|
import {Form} from "@/components/ui/form"
|
||||||
|
import {Button} from "@/components/ui/button";
|
||||||
|
import {useMutation} from "@tanstack/react-query";
|
||||||
|
import {ProjectSchema} from "@/components/wrappers/Project/ProjectForm.schema";
|
||||||
|
import {createProjectAction} from "@/components/wrappers/Project/ProjectForm.action";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import {Database, Organization} from "@prisma/client"
|
||||||
|
import {MultiSelect} from "@/components/wrappers/MultiSelect/MultiSelect";
|
||||||
|
import {ZodString} from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
export type projectFormProps = {
|
||||||
|
defaultValues?: ProjectSchema;
|
||||||
|
databases: Database[],
|
||||||
|
organization: Organization,
|
||||||
|
projectId?: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectForm = (props: projectFormProps) => {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const isCreate = !Boolean(props.defaultValues)
|
||||||
|
|
||||||
|
|
||||||
|
const formatDatabasesList = (databases: Database[]) => {
|
||||||
|
return databases.map(database => ({
|
||||||
|
value: database.id,
|
||||||
|
label: `${database.name} | ${database.generatedId}`,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDefaultDatabases = (databases: ProjectSchema["databases"]): string[] => {
|
||||||
|
return databases.map(database => database.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedDefaultValues = {
|
||||||
|
...props.defaultValues,
|
||||||
|
databases: !isCreate ? formatDefaultDatabases(props.defaultValues?.databases) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const form = useZodForm({
|
||||||
|
schema: ProjectSchema,
|
||||||
|
defaultValues: formattedDefaultValues,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (values: ProjectSchema) => {
|
||||||
|
console.log(values)
|
||||||
|
const projectCreated = await createProjectAction({data: values, organizationId: props.organization.id});
|
||||||
|
console.log(projectCreated)
|
||||||
|
//
|
||||||
|
// if (data) {
|
||||||
|
// toast.success(`Success`);
|
||||||
|
// router.push(`/dashboard/projects/${data.id}`);
|
||||||
|
// router.refresh()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (validationErrors) {
|
||||||
|
// toast.success(`Error`);
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Form form={form}
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
onSubmit={async (values) => {
|
||||||
|
await mutation.mutateAsync(values);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
defaultValue=""
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="Project 1" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="slug"
|
||||||
|
defaultValue=""
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Slug</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder="project-1" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="databases"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Databases</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
|
||||||
|
<MultiSelect
|
||||||
|
options={formatDatabasesList(props.databases)}
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value ?? []}
|
||||||
|
placeholder="Select databases"
|
||||||
|
variant="inverted"
|
||||||
|
animation={2}
|
||||||
|
// maxCount={100}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>Select databases you want to add to this project</FormDescription>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button>
|
||||||
|
Create Project
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -20,12 +20,14 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover"
|
} from "@/components/ui/popover"
|
||||||
import {FormControl} from "@/components/ui/form";
|
import {FormControl} from "@/components/ui/form";
|
||||||
|
import {SidebarMenuButton} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
export type comboBoxProps = {
|
export type comboBoxProps = {
|
||||||
values: Array<{ value: string, label: string }>
|
values: Array<{ value: string, label: string }>
|
||||||
defaultValue?: string
|
defaultValue?: string
|
||||||
onValueChange?: any
|
onValueChange?: any
|
||||||
searchField?: boolean
|
searchField?: boolean
|
||||||
|
sideBar?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -41,19 +43,33 @@ export function ComboBox(props: comboBoxProps) {
|
|||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
{props.sideBar ?
|
||||||
variant="outline"
|
<SidebarMenuButton>
|
||||||
role="combobox"
|
{value
|
||||||
aria-expanded={open}
|
? choices.find((choice) => choice.value === value)?.label
|
||||||
className="w-full justify-between"
|
: "Select choice..."}
|
||||||
>
|
<ChevronDown className="ml-auto"/>
|
||||||
{value
|
</SidebarMenuButton>
|
||||||
? choices.find((choice) => choice.value === value)?.label
|
:
|
||||||
: "Select choice..."}
|
<Button
|
||||||
<ChevronDown className="opacity-50"/>
|
variant="outline"
|
||||||
</Button>
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
className="w-full justify-between"
|
||||||
|
>
|
||||||
|
{value
|
||||||
|
? choices.find((choice) => choice.value === value)?.label
|
||||||
|
: "Select choice..."}
|
||||||
|
<ChevronDown className="opacity-50"/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="p-0 popover-content-width-full">
|
<PopoverContent
|
||||||
|
|
||||||
|
className="p-0 popover-content-width-full"
|
||||||
|
>
|
||||||
<Command>
|
<Command>
|
||||||
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
{searchField ? <CommandInput placeholder="Search choice..." className="h-9"/> : null}
|
||||||
<CommandList>
|
<CommandList>
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
"use server"
|
|
||||||
|
|
||||||
import {userAction} from "@/safe-actions";
|
|
||||||
import {prisma} from "@/prisma";
|
|
||||||
import {ProjectSchema} from "@/components/wrappers/project/ProjectForm.schema";
|
|
||||||
|
|
||||||
|
|
||||||
export const createProjectAction = userAction
|
|
||||||
.schema(ProjectSchema)
|
|
||||||
.action(async ({parsedInput, ctx}) => {
|
|
||||||
// Verify if slug already exist
|
|
||||||
// await verifySlugUniqueness(parsedInput.slug);
|
|
||||||
|
|
||||||
console.log("ctx", ctx)
|
|
||||||
return prisma.project.create({
|
|
||||||
data: {
|
|
||||||
...parsedInput,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
|
||||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
|
||||||
import {Input} from "@/components/ui/input";
|
|
||||||
import {Form} from "@/components/ui/form"
|
|
||||||
import {Button} from "@/components/ui/button";
|
|
||||||
import {useMutation} from "@tanstack/react-query";
|
|
||||||
import {ProjectSchema} from "@/components/wrappers/project/ProjectForm.schema";
|
|
||||||
import {createProjectAction} from "@/components/wrappers/project/ProjectForm.action";
|
|
||||||
import {toast} from "sonner";
|
|
||||||
import {useRouter} from "next/navigation";
|
|
||||||
import {useSession} from "next-auth/react";
|
|
||||||
|
|
||||||
export type projectFormProps = {
|
|
||||||
defaultValues?: ProjectSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProjectForm = (props: projectFormProps) => {
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const {data: session} = useSession()
|
|
||||||
console.log("organization", session)
|
|
||||||
|
|
||||||
const form = useZodForm({
|
|
||||||
schema: ProjectSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mutation = useMutation({
|
|
||||||
mutationFn: async (values: ProjectSchema) => {
|
|
||||||
|
|
||||||
const {data, validationErrors} = await createProjectAction({...values, organizationId: organization?.id});
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
toast.success(`Success`);
|
|
||||||
router.push(`/dashboard/projects/${data.id}`);
|
|
||||||
router.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validationErrors) {
|
|
||||||
toast.success(`Error`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Form form={form}
|
|
||||||
className="flex flex-col gap-4"
|
|
||||||
onSubmit={async (values) => {
|
|
||||||
await mutation.mutateAsync(values);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="name"
|
|
||||||
defaultValue=""
|
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="Project 1" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage/>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="slug"
|
|
||||||
defaultValue=""
|
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Slug</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
placeholder="project-1" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage/>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button>
|
|
||||||
Create Project
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -74,7 +74,7 @@ async function createDefaultOrganization() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!defaultOrganization) {
|
if (!defaultOrganization) {
|
||||||
console.log("==== Creating default organization... ====\n")
|
console.log("==== Creating default Organization... ====\n")
|
||||||
await prisma.organization.create({
|
await prisma.organization.create({
|
||||||
data: {
|
data: {
|
||||||
...defaultOrganizationConf
|
...defaultOrganizationConf
|
||||||
|
|||||||
Reference in New Issue
Block a user