Skip to content

Commit

Permalink
feat(kampus): new era with clerk & radix-ui themes (#717)
Browse files Browse the repository at this point in the history
  • Loading branch information
usirin authored Dec 28, 2023
1 parent 0410758 commit 69feccf
Show file tree
Hide file tree
Showing 62 changed files with 16,295 additions and 22,286 deletions.
4 changes: 4 additions & 0 deletions apps/gql/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
NODE_ENV=development
DATABASE_URL=mysql://kampus:kampus@localhost:3306/kampus?schema=public

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SECRET=
102 changes: 102 additions & 0 deletions apps/gql/app/api/webhooks/clerk/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { headers } from "next/headers";
import { type WebhookEvent } from "@clerk/nextjs/server";
import { Webhook } from "svix";

import { createClients } from "~/clients";
import { env } from "~/env";

export const dynamic = "force-dynamic";

const { prisma: db } = createClients();

export async function POST(req: Request) {
// Get the headers
const headerPayload = headers();
const svix_id = headerPayload.get("svix-id");
const svix_timestamp = headerPayload.get("svix-timestamp");
const svix_signature = headerPayload.get("svix-signature");

// If there are no headers, error out
if (!svix_id || !svix_timestamp || !svix_signature) {
return new Response("Error occured -- no svix headers", {
status: 400,
});
}

// Get the body
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = await req.json();
const body = JSON.stringify(payload);

// Create a new SVIX instance with your secret.
const wh = new Webhook(env.CLERK_WEBHOOK_SECRET);

let evt: WebhookEvent;

// Verify the payload with the headers
try {
evt = wh.verify(body, {
"svix-id": svix_id,
"svix-timestamp": svix_timestamp,
"svix-signature": svix_signature,
}) as WebhookEvent;
} catch (err) {
console.error("Error verifying webhook:", err);
return new Response("Error occured", {
status: 400,
});
}

if (evt) {
// 👉 Parse the incoming event body into a ClerkWebhook object
try {
// 👉 `webhook.type` is a string value that describes what kind of event we need to handle

// 👉 If the type is "user.updated" the important values in the database will be updated in the users table
if (evt.type === "user.updated") {
const email = evt.data.email_addresses[0]?.email_address;
if (email) {
await db.user.update({
where: { id: evt.data.id },
data: {
username: evt.data.username || "",
name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim() || null,
image: evt.data.image_url,
email,
},
});
}
}

// 👉 If the type is "user.created" create a record in the users table
if (evt.type === "user.created") {
const email = evt.data.email_addresses[0]?.email_address;
if (email) {
await db.user.create({
data: {
id: evt.data.id,
username: evt.data.username || "",
name: `${evt.data.first_name ?? ""} ${evt.data.last_name ?? ""}`.trim() || null,
image: evt.data.image_url,
email,
},
});
}
}

// 👉 If the type is "user.deleted", delete the user record and associated blocks
if (evt.type === "user.deleted") {
await db.user.delete({
where: { id: evt.data.id },
});
}

return new Response("", { status: 201 });
} catch (err) {
console.error(err);
return new Response("Error occured -- processing webhook data", {
status: 500,
});
}
}
}
10 changes: 8 additions & 2 deletions apps/gql/app/graphql/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { currentUser } from "@clerk/nextjs";
import { createSchema, createYoga } from "graphql-yoga";

import { createActions } from "~/actions";
Expand All @@ -11,19 +12,24 @@ import { type KampusGQLContext } from "~/schema/types";
const typeDefs = readFileSync(join(process.cwd(), "schema/schema.graphql"), "utf8").toString();
const clients = createClients();

async function getUserSession() {
const user = await currentUser();
return user ? { user: { id: user.id } } : null;
}

const { handleRequest } = createYoga<KampusGQLContext>({
schema: createSchema<KampusGQLContext>({ typeDefs, resolvers }),
logging: "debug",
graphiql: true,
context: () => {
context: async () => {
const loaders = createLoaders(clients);
const actions = createActions(clients);

return {
loaders,
actions,
pasaport: {
session: null,
session: await getUserSession(),
},
} satisfies KampusGQLContext;
},
Expand Down
5 changes: 5 additions & 0 deletions apps/gql/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export const env = parseEnv(
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL,
KAMPUS_ENV: process.env.KAMPUS_ENV,
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
CLERK_WEBHOOK_SECRET: process.env.CLERK_WEBHOOK_SECRET,
},
{
NODE_ENV: z.enum(["development", "test", "production"]),
Expand All @@ -14,5 +17,7 @@ export const env = parseEnv(
.url()
.default("mysql://kampus:kampus@localhost:3306/kampus?schema=public&connect_timeout=300"),
KAMPUS_ENV: z.enum(["development", "test", "production"]).default("development"),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().default("clerk-pub-key"),
CLERK_WEBHOOK_SECRET: z.string().default("clerk-webhook-secret"),
}
);
37 changes: 0 additions & 37 deletions apps/gql/fly.toml

This file was deleted.

10 changes: 10 additions & 0 deletions apps/gql/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({
// "/" will be accessible to all users
publicRoutes: ["/", "/graphql", "/api/webhooks(.*)"],
});

export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|graphql)(.*)"],
};
2 changes: 2 additions & 0 deletions apps/gql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"vitest-mock-extended": "1.1.3"
},
"dependencies": {
"@clerk/nextjs": "4.27.5",
"@graphql-tools/schema": "9.0.19",
"@kampus/gql-utils": "*",
"@kampus/prisma": "*",
Expand All @@ -40,6 +41,7 @@
"graphql-scalars": "1.21.3",
"graphql-yoga": "3.9.1",
"object-hash": "3.0.0",
"svix": "1.15.0",
"znv": "0.3.2",
"zod": "3.21.4"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/gql/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export interface KampusGQLContext {
loaders: DataLoaders;
actions: DataActions;
pasaport: {
session: null | { id: string; user: { id: string } };
session: null | { user: { id: string } };
};
}
3 changes: 3 additions & 0 deletions apps/kampus/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ NEXT_PUBLIC_GQL_URL=http://gql.localhost.kamp.us:4000/graphql
NEXT_PUBLIC_KAMPUS_ENV=localhost

RESEND_API_KEY="get-your-own-resend-api-key"

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
16 changes: 16 additions & 0 deletions apps/kampus/app/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client";

import { type PropsWithChildren } from "react";
import { Theme, ThemePanel } from "@radix-ui/themes";

import "@radix-ui/themes/styles.css";

import { ThemeProvider as KampusThemeProvider } from "@kampus/ui";

export function ThemeProvider(props: PropsWithChildren) {
return (
<KampusThemeProvider attribute="class" defaultTheme="system" enableSystem>
<Theme>{props.children}</Theme>
</KampusThemeProvider>
);
}
21 changes: 11 additions & 10 deletions apps/kampus/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,26 @@ import { RelayEnvironmentProvider } from "~/features/relay/RelayEnvironmentProvi

import "./globals.css";

import { ThemeProvider } from "@kampus/ui";
import { cn } from "~/../../packages/ui/utils";
import { trTR } from "@clerk/localizations";
import { ClerkProvider } from "@clerk/nextjs";

import { ThemeProvider } from "./ThemeProvider";

const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });

export const metadata = {
title: "kamp.us",
description: "topluluk",
description: "dijital enstitü",
};

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
inter.variable
)}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<RelayEnvironmentProvider>{children}</RelayEnvironmentProvider>
<html lang="tr">
<body className={inter.variable}>
<ThemeProvider>
<ClerkProvider localization={trTR}>
<RelayEnvironmentProvider>{children}</RelayEnvironmentProvider>
</ClerkProvider>
<Toaster />
</ThemeProvider>
</body>
Expand Down
5 changes: 2 additions & 3 deletions apps/kampus/app/odin/mufredat/[[...lesson]]/OdinLesson.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import { graphql, usePreloadedQuery } from "react-relay";

import { type SerializablePreloadedQuery } from "@kampus/relay";
import useSerializablePreloadedQuery from "@kampus/relay/use-serializable-preloaded-query";

import { type SerializablePreloadedQuery } from "~/features/relay";
import useSerializablePreloadedQuery from "~/features/relay/use-serializable-preloaded-query";
import { type OdinLessonQuery } from "~/app/odin/mufredat/[[...lesson]]/__generated__/OdinLessonQuery.graphql";
import { OdinLessonActions } from "./OdinLessonActions";
import { OdinLessonBody } from "./OdinLessonBody";
Expand Down
3 changes: 1 addition & 2 deletions apps/kampus/app/odin/mufredat/[[...lesson]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import loadSerializableQuery from "@kampus/relay/load-serializable-query";

import loadSerializableQuery from "~/features/relay/load-serializable-query";
import OdinLessonQueryNode, {
type OdinLessonQuery,
} from "~/app/odin/mufredat/[[...lesson]]/__generated__/OdinLessonQuery.graphql";
Expand Down
19 changes: 12 additions & 7 deletions apps/kampus/app/pano/@modal/(.)post/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
"use client";

import { useRouter } from "next/navigation";

import { Dialog, DialogContent } from "@kampus/ui";
import { Dialog, Theme } from "@radix-ui/themes";

import { CreatePanoPostForm } from "~/app/pano/CreatePanoPostForm";

export default function CreatePost({ searchParams }: { searchParams: { conn: string } }) {
const router = useRouter();

return (
<Dialog open onOpenChange={() => router.back()}>
<DialogContent className="sm:max-w-[425px]">
<CreatePanoPostForm connectionID={searchParams.conn} onCompleted={() => router.back()} />
</DialogContent>
</Dialog>
<Theme accentColor="amber">
<Dialog.Root open onOpenChange={() => router.back()}>
<Dialog.Content className="sm:max-w-[425px]">
<Dialog.Title>Yeni Pano Girdisi</Dialog.Title>
<Dialog.Description size="2" mb="4">
pano&apos;yu zenginleştir :)
</Dialog.Description>
<CreatePanoPostForm connectionID={searchParams.conn} onCompleted={() => router.back()} />
</Dialog.Content>
</Dialog.Root>
</Theme>
);
}
Loading

0 comments on commit 69feccf

Please sign in to comment.