Skip to content

Commit

Permalink
🔄 Merge pull request #10 from steeeee0223/feature/worxpace
Browse files Browse the repository at this point in the history
Setup sidebar & actions (create/get docs)
  • Loading branch information
steeeee0223 authored Jan 25, 2024
2 parents 11ecc33 + 52ccb99 commit 1c389d5
Show file tree
Hide file tree
Showing 19 changed files with 436 additions and 23 deletions.
37 changes: 37 additions & 0 deletions apps/worxpace/src/actions/create-document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use server";

import { revalidatePath } from "next/cache";

import { type Document } from "@acme/prisma";
import { createSafeAction, type ActionHandler } from "@acme/ui/lib";
import { CreateDocument, type CreateDocumentInput } from "@acme/validators";

import {
createDocument as $create,
createAuditLog,
fetchClient,
UnauthorizedError,
} from "~/lib";

const handler: ActionHandler<CreateDocumentInput, Document> = async (data) => {
let result;

try {
const { userId, orgId, path } = fetchClient();
result = await $create({ ...data, userId, orgId });
/** Activity Log */
await createAuditLog(
{ title: result.title, entityId: result.id, type: "DOCUMENT" },
"CREATE",
);
revalidatePath(path);
} catch (error) {
if (error instanceof UnauthorizedError) return { error: "Unauthorized" };
console.log(`ERROR`, error);
return { error: "Failed to create document." };
}

return { data: result };
};

export const createDocument = createSafeAction(CreateDocument, handler);
1 change: 1 addition & 0 deletions apps/worxpace/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./create-document"
Original file line number Diff line number Diff line change
@@ -1,11 +1,68 @@
"use client";

import Image from "next/image";
import { useOrganization, useUser } from "@clerk/nextjs";
import { PlusCircle } from "lucide-react";

import { Button, useTreeAction } from "@acme/ui/components";
import { useAction } from "@acme/ui/hooks";
import { cn } from "@acme/ui/lib";

import { createDocument } from "~/actions";
import { theme } from "~/constants/theme";

interface Params {
params: { role: string; clientId: string };
}

const Client = ({ params: { role, clientId } }: Params) => {
const Client = ({ params: { role } }: Params) => {
const { user } = useUser();
const { organization } = useOrganization();
const name = role === "organization" ? organization?.name : user?.firstName;

const { dispatch } = useTreeAction();
const { execute } = useAction(createDocument, {
onSuccess: (data) => {
const { id, title, isArchived, parentId } = data;
console.log(`Document created: ${title}`);
dispatch({ type: "add", payload: [{ id, title, isArchived, parentId }] });
},
onError: (e) => console.log(e),
});
const onSubmit = () => {
execute({ title: "Untitled", parentId: undefined })
.then(() => console.log(`processing`))
.catch((e) => console.log(e));
};

return (
<div>
Client {role} - {clientId}
<div
className={cn(
theme.flex.center,
"h-full flex-col justify-center space-y-4",
)}
>
<Image
src="/empty.png"
height="300"
width="300"
alt="Empty"
className="dark:hidden"
/>
<Image
src="/empty-dark.png"
height="300"
width="300"
alt="Empty"
className="hidden dark:block"
/>
<h2 className="text-lg font-medium">Welcome to {name}&apos;s WorXpace</h2>
<form action={onSubmit}>
<Button type="submit">
<PlusCircle className={cn(theme.size.icon, "mr-2")} />
Create a note
</Button>
</form>
</div>
);
};
Expand Down
42 changes: 42 additions & 0 deletions apps/worxpace/src/app/(platform)/(tools)/_components/doc-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { OrganizationSwitcher } from "@clerk/nextjs";

interface DocListProps {
isMobile?: boolean;
}

const DocList = ({ isMobile }: DocListProps) => {
return (
<>
<div>
<OrganizationSwitcher
afterSelectPersonalUrl="/personal/:id"
afterCreateOrganizationUrl="/organization/:id"
afterSelectOrganizationUrl="/organization/:id"
afterLeaveOrganizationUrl="/select-role"
appearance={{
elements: {
rootBox: {
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: 6,
padding: "14px 8px",
},
avatarBox: {
borderRadius: 9999,
height: "20px",
width: "20px",
},
organizationSwitcherPopoverCard: {
zIndex: 99999,
},
},
}}
/>
</div>
<div className="mt-4">Doc Items</div>
</>
);
};

export default DocList;
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useNavControl } from "@acme/ui/hooks";
import { cn } from "@acme/ui/lib";

import { theme } from "~/constants/theme";
import DocList from "./doc-list";

export const Sidebar = () => {
const pathname = usePathname();
Expand Down Expand Up @@ -54,6 +55,7 @@ export const Sidebar = () => {
>
<ChevronsLeft className="h-6 w-6" />
</div>
<DocList isMobile={isMobile} />
<div
onMouseDown={handleMouseDown}
onClick={resetWidth}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"use client";

import { SignOutButton, useOrganizationList, useUser } from "@clerk/nextjs";
import { ChevronsLeftRight } from "lucide-react";

import {
Avatar,
AvatarImage,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@acme/ui/components";
import { cn } from "@acme/ui/lib";

import { theme } from "~/constants/theme";

interface UserItemProps {
name?: string | null;
imageUrl?: string | null;
}
const UserItem = ({ name, imageUrl }: UserItemProps) => {
return (
<div className={theme.flex.gap2}>
<div className="rounded-md bg-secondary p-1">
<Avatar className="h-8 w-8">
<AvatarImage src={imageUrl ?? undefined} />
</Avatar>
</div>
<div className="space-y-1">
<p className="line-clamp-1 text-sm">{name ?? "User"}&apos;s WorXpace</p>
</div>
</div>
);
};

/** @deprecated */
export const UserItems = () => {
const { user } = useUser();
const { userMemberships } = useOrganizationList({
userMemberships: { infinite: true },
});

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div
role="button"
className={cn(
theme.flex.center,
"w-full p-3 text-sm hover:bg-primary/5",
)}
>
<div className={cn(theme.flex.gap2, "max-w-[150px]")}>
<Avatar className="h-5 w-5">
<AvatarImage src={user?.imageUrl} />
</Avatar>
<span className="line-clamp-1 text-start font-medium">
{user?.fullName}
</span>
</div>
<ChevronsLeftRight
className={cn(
theme.size.icon,
"ml-2 rotate-90 text-muted-foreground",
)}
/>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
className="z-[99999] w-80"
align="start"
alignOffset={11}
forceMount
>
<div className="flex flex-col space-y-4 p-2">
<p className="text-xs font-medium leading-none text-muted-foreground">
{user?.emailAddresses[0].emailAddress}
</p>
<UserItem name={user?.fullName} imageUrl={user?.imageUrl} />
{userMemberships.data?.map(
({ organization: { id, name, imageUrl } }) => (
<UserItem key={id} name={name} imageUrl={imageUrl} />
),
)}
</div>
<DropdownMenuSeparator />
<DropdownMenuItem
asChild
className="w-full cursor-pointer text-muted-foreground"
>
<SignOutButton>Log out</SignOutButton>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};
32 changes: 27 additions & 5 deletions apps/worxpace/src/app/(platform)/(tools)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
import { type PropsWithChildren } from "react";
import { redirect } from "next/navigation";
import { auth } from "@clerk/nextjs";

import { TreeProvider } from "@acme/ui/components";

import { fetchClient, fetchDocuments, isAuthenticated } from "~/lib";
import { Sidebar } from "./_components/sidebar";

const ToolsLayout = ({ children }: PropsWithChildren) => {
const { userId } = auth();
if (!userId) redirect("/select-role");
if (!isAuthenticated()) redirect("/select-role");

const fetchItems = async () => {
"use server";
try {
const { userId, orgId } = fetchClient();
const documents = await fetchDocuments(userId, orgId, false);
console.log(`docs:`, documents);
const data = documents.map(({ id, title, parentId, isArchived }) => ({
id,
title,
parentId,
isArchived,
}));
return { data };
} catch {
return { error: `Error occurred while fetching documents` };
}
};

return (
<div className="flex h-full dark:bg-[#1F1F1F]">
<TreeProvider
className="flex h-full dark:bg-[#1F1F1F]"
fetchItems={fetchItems}
>
<Sidebar />
<main className="h-full flex-1 overflow-y-auto">{children}</main>
</div>
</TreeProvider>
);
};

Expand Down
10 changes: 8 additions & 2 deletions apps/worxpace/src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ export const env = createEnv({
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {},
server: {
DB_AUTH: z.string(),
DB_WORXPACE: z.string(),
},
/**
* Specify your client-side environment variables schema here.
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
Expand All @@ -44,7 +47,10 @@ export const env = createEnv({
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL: process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL,
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
// DB
DB_AUTH: process.env.DB_AUTH,
DB_WORXPACE: process.env.DB_WORXPACE,

},
skipValidation:
Expand Down
22 changes: 22 additions & 0 deletions apps/worxpace/src/lib/documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use server";

import { worxpace as db, type Document } from "@acme/prisma";
import { CreateDocumentInput } from "@acme/validators";

export const createDocument = async (
data: CreateDocumentInput & { userId: string; orgId: string | null },
): Promise<Document> =>
await db.document.create({
data: { ...data, isArchived: false, isPublished: false },
});

export const fetchDocuments = async (
userId: string,
orgId: string | null,
isArchived?: boolean,
parentId?: string | null,
): Promise<Document[]> =>
await db.document.findMany({
where: { userId, orgId, parentId, isArchived },
orderBy: { createdAt: "desc" },
});
11 changes: 11 additions & 0 deletions apps/worxpace/src/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class UnauthorizedError extends Error {
constructor() {
super("Unauthorized");
}
}

export class NotFound extends Error {
constructor() {
super("Not Found");
}
}
4 changes: 4 additions & 0 deletions apps/worxpace/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "./documents";
export * from "./errors";
export * from "./logs";
export * from "./types";
export * from "./utils";
Loading

0 comments on commit 1c389d5

Please sign in to comment.