Skip to content

Commit

Permalink
Add Messaging & Tickets (#54)
Browse files Browse the repository at this point in the history
Initial merge of messaging & tickets. Will have a additional merge in the future, this is to introduce groundwork and schema changes. 

* Add socketii, pusher, and trpc

* remove realtime package

* Migrations

* Fix trpc, add db relations

* Temp hide tickets

* Format + fix lockfile
  • Loading branch information
Lermatroid authored Jul 22, 2024
1 parent 431030c commit 8361616
Show file tree
Hide file tree
Showing 23 changed files with 2,870 additions and 47 deletions.
6 changes: 6 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@
"@radix-ui/react-switch": "^1.0.3",
"@react-email/components": "^0.0.21",
"@t3-oss/env-nextjs": "^0.10.1",
"@tanstack/react-query": "^5.51.11",
"@tanstack/react-table": "^8.19.3",
"@trpc/client": "11.0.0-rc.466",
"@trpc/next": "11.0.0-rc.466",
"@trpc/react-query": "11.0.0-rc.466",
"@trpc/server": "11.0.0-rc.466",
"@types/node": "20.14.11",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
Expand Down Expand Up @@ -62,6 +67,7 @@
"pg": "^8.12.0",
"postcss": "8.4.39",
"postgres": "^3.4.4",
"pusher-js": "8.4.0-rc2",
"react": "18.3.1",
"react-aria": "^3.33.1",
"react-bootstrap": "^2.10.4",
Expand Down
34 changes: 34 additions & 0 deletions apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";

import { env } from "@/env";
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";

/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a HTTP request (e.g. when you make requests from Client Components).
*/
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});

export { handler as GET, handler as POST };
94 changes: 49 additions & 45 deletions apps/web/src/app/dash/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { redirect } from "next/navigation";
import ProfileButton from "@/components/shared/ProfileButton";
import ClientToast from "@/components/shared/ClientToast";

import { TRPCReactProvider } from "@/trpc/react";

interface DashLayoutProps {
children: React.ReactNode;
}
Expand All @@ -22,53 +24,55 @@ export default async function DashLayout({ children }: DashLayoutProps) {

return (
<>
<ClientToast />
<div className="grid h-16 w-full grid-cols-2 bg-nav px-5">
<div className="flex items-center gap-x-4">
<Image
src={c.icon.svg}
alt={c.hackathonName + " Logo"}
width={32}
height={32}
/>
<div className="h-[45%] w-[2px] rotate-[25deg] bg-muted-foreground" />
<h2 className="font-bold tracking-tight">Dashboard</h2>
<TRPCReactProvider>
<ClientToast />
<div className="grid h-16 w-full grid-cols-2 bg-nav px-5">
<div className="flex items-center gap-x-4">
<Image
src={c.icon.svg}
alt={c.hackathonName + " Logo"}
width={32}
height={32}
/>
<div className="h-[45%] w-[2px] rotate-[25deg] bg-muted-foreground" />
<h2 className="font-bold tracking-tight">Dashboard</h2>
</div>
<div className="hidden items-center justify-end gap-x-4 md:flex">
<Link href={"/"}>
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Home
</Button>
</Link>
<Link href={c.links.guide} target="_blank">
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Survival Guide
</Button>
</Link>
<Link href={c.links.discord} target="_blank">
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Discord
</Button>
</Link>
<ProfileButton />
</div>
<div className="flex items-center justify-end gap-x-4 md:hidden"></div>
</div>
<div className="hidden items-center justify-end gap-x-4 md:flex">
<Link href={"/"}>
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Home
</Button>
</Link>
<Link href={c.links.guide} target="_blank">
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Survival Guide
</Button>
</Link>
<Link href={c.links.discord} target="_blank">
<Button
variant={"outline"}
className="bg-nav hover:bg-background"
>
Discord
</Button>
</Link>
<ProfileButton />
<div className="flex h-12 w-full border-b border-b-border bg-nav px-5">
{Object.entries(c.dashPaths.dash).map(([name, path]) => (
<DashNavItem key={name} name={name} path={path} />
))}
</div>
<div className="flex items-center justify-end gap-x-4 md:hidden"></div>
</div>
<div className="flex h-12 w-full border-b border-b-border bg-nav px-5">
{Object.entries(c.dashPaths.dash).map(([name, path]) => (
<DashNavItem key={name} name={name} path={path} />
))}
</div>
{children}
{children}
</TRPCReactProvider>
</>
);
}
Expand Down
14 changes: 14 additions & 0 deletions apps/web/src/app/dash/tickets/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TicketList from "@/components/dash/tickets/TicketList";

export default function TicketsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="mx-auto grid h-[calc(100vh-7rem)] w-screen grid-cols-5 gap-x-3">
<TicketList />
<div className="col-span-4 h-full">{children}</div>
</div>
);
}
60 changes: 60 additions & 0 deletions apps/web/src/app/dash/tickets/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";
import { useState } from "react";
import { Input } from "@/components/shadcn/ui/input";
import { Label } from "@/components/shadcn/ui/label";
import { Textarea } from "@/components/shadcn/ui/textarea";
import { Button } from "@/components/shadcn/ui/button";
import { api } from "@/trpc/react";
import { toast } from "sonner";

export default function Page() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");

const createTicket = api.tickets.create.useMutation();

async function runCreateTicket() {
if (title.length > 3 && description.length > 10) {
const result = await createTicket.mutateAsync({
title,
description,
});
if (result.success) {
toast.success("Ticket created successfully!");
console.log(
"created ticket with ID " +
result.ticketID +
" and chat with ID " +
result.chatID,
);
}
} else {
toast.error(
"Your title or description is too short! Please try again.",
);
}
}

return (
<div className="h-full pt-20">
<div className="mx-auto max-w-3xl">
<h1 className="text-3xl font-black">New Ticket</h1>
<div className="flex flex-col items-start gap-y-5 pt-10">
<div className="w-full">
<Label className="pb-2">Title</Label>
<Input onChange={(e) => setTitle(e.target.value)} />
</div>
<div className="w-full">
<Label className="mb-1">Description</Label>
<Textarea
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<Button onClick={() => runCreateTicket()}>
Create Ticket
</Button>
</div>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions apps/web/src/app/dash/tickets/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Page() {
return (
<main className="flex h-full items-center justify-center">
No Ticket Selected
</main>
);
}
43 changes: 43 additions & 0 deletions apps/web/src/components/dash/tickets/TicketList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"use client";

import { title } from "process";

const dummyTickets = [
{
title: "Ticket 1",
lastMessage: "howdy this is a message",
lastMessageDate: "2023-05-01",
lastMessageAuthor: "@user",
},
{
title: "Ticket 2",
lastMessage: "howdy this is a message 2",
lastMessageDate: "2023-05-01",
lastMessageAuthor: "@user",
},
{
title: "Ticket 3",
lastMessage: "howdy this is a message 3",
lastMessageDate: "2023-05-01",
lastMessageAuthor: "@user",
},
];

export default function TicketList() {
return (
<div className="border-r border-r-muted">
{dummyTickets.map((ticket) => (
<TicketItem key={ticket.title} />
))}
</div>
);
}

function TicketItem() {
return (
<div className="flex h-16 flex-col justify-center border-b border-b-muted px-5 py-4">
<h3 className="text-sm font-bold">Ticket Name</h3>
<p className="text-xs text-muted-foreground">Last Message</p>
</div>
);
}
3 changes: 3 additions & 0 deletions apps/web/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const env = createEnv({
AWS_SES_EMAIL_FROM: z.string().min(1),
INTERNAL_AUTH_KEY: z.string().min(64),
BOT_API_URL: z.string().min(1),
NODE_ENV: z
.enum(["development", "test", "production"])
.default("development"),
},
client: {
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
Expand Down
23 changes: 23 additions & 0 deletions apps/web/src/server/api/root.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ticketsRouter } from "@/server/api/routers/tickets";
import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";

/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
tickets: ticketsRouter,
});

// export type definition of API
export type AppRouter = typeof appRouter;

/**
* Create a server-side caller for the tRPC API.
* @example
* const trpc = createCaller(createContext);
* const res = await trpc.post.all();
* ^? Post[]
*/
export const createCaller = createCallerFactory(appRouter);
31 changes: 31 additions & 0 deletions apps/web/src/server/api/routers/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { z } from "zod";

import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";

export const postRouter = createTRPCRouter({
hello: publicProcedure
.input(z.object({ text: z.string() }))
.query(({ input }) => {
return {
greeting: `Hello ${input.text}`,
};
}),

create: publicProcedure
.input(z.object({ name: z.string().min(1) }))
.mutation(async ({ ctx, input }) => {
// simulate a slow db call
await new Promise((resolve) => setTimeout(resolve, 1000));

// await ctx.db.insert(posts).values({
// name: input.name,
// });
}),

getLatest: publicProcedure.query(({ ctx }) => {
// return ctx.db.query.posts.findFirst({
// orderBy: (posts, { desc }) => [desc(posts.createdAt)],
// });
return null;
}),
});
Loading

0 comments on commit 8361616

Please sign in to comment.