From 7d2bdad3956cf235fe823aaae669976b4ac42b5a Mon Sep 17 00:00:00 2001 From: xiften <108333567+not-ani@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:02:47 -0600 Subject: [PATCH 1/7] feat: base of permissions --- apps/nextjs/package.json | 2 +- apps/nextjs/src/app/(auth)/sign-in/page.tsx | 68 +++++++++++++++ apps/nextjs/src/app/(auth)/sign-out/page.tsx | 69 +++++++++++++++ .../project/[id]/(root)/layout.tsx | 10 ++- .../permissions/_components/add-user-form.tsx | 84 +++++++++++++++++++ .../permissions/_components/add-user.tsx | 13 +++ .../project/[id]/(root)/permissions/page.tsx | 1 - .../project/[id]/(tasks)/layout.tsx | 5 ++ apps/nextjs/src/app/api/project/[id]/route.ts | 68 +++++++++++++-- packages/api/src/root.ts | 2 + packages/api/src/router/users.ts | 32 +++++++ packages/auth/src/actions.ts | 48 +++++++++++ packages/auth/src/config.ts | 51 +++++------ packages/auth/src/permissions.ts | 7 +- packages/db/src/perms.ts | 3 + packages/db/src/schema.ts | 39 ++++++--- packages/validators/src/index.ts | 7 ++ 17 files changed, 461 insertions(+), 48 deletions(-) create mode 100644 apps/nextjs/src/app/(auth)/sign-in/page.tsx create mode 100644 apps/nextjs/src/app/(auth)/sign-out/page.tsx create mode 100644 apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx create mode 100644 apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx create mode 100644 packages/api/src/router/users.ts create mode 100644 packages/auth/src/actions.ts create mode 100644 packages/db/src/perms.ts diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 8dcdf0a..a9cd739 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "pnpm with-env next build", "clean": "git clean -xdf .next .turbo node_modules", - "dev": "pnpm with-env next dev --turbo", + "dev": "pnpm with-env next dev ", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint", "start": "pnpm with-env next start", diff --git a/apps/nextjs/src/app/(auth)/sign-in/page.tsx b/apps/nextjs/src/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000..dfe0ac4 --- /dev/null +++ b/apps/nextjs/src/app/(auth)/sign-in/page.tsx @@ -0,0 +1,68 @@ +import { signIn } from "@amaxa/auth" +import { Button } from "@amaxa/ui/button" +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@amaxa/ui/card" +import { Input } from "@amaxa/ui/input" +import { Label } from "@amaxa/ui/label" +import Link from "next/link" +import { env } from "~/env" + + +export default function LoginForm() { + return ( +
+ + + Login + + Enter your email below to login to your account + + + +
+ { + env.NODE_ENV === "development" ? ( + <> + +
{ + "use server" + await signIn("email", { + password: data.get("password"), + redirectTo: "/" + }) + }} + > +
+ + +
+ +
+ + ) : null + } + +
{ + "use server" + await signIn("google", { redirectTo: "/" }) + }} + > + +
+
+
+ Have question's about how we use your data?{" "} + + Read our privacy statement here + +
+
+
+
+ ) +} diff --git a/apps/nextjs/src/app/(auth)/sign-out/page.tsx b/apps/nextjs/src/app/(auth)/sign-out/page.tsx new file mode 100644 index 0000000..c5a0d02 --- /dev/null +++ b/apps/nextjs/src/app/(auth)/sign-out/page.tsx @@ -0,0 +1,69 @@ +import { signIn } from '@amaxa/auth' +import { Button } from '@amaxa/ui/button' +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@amaxa/ui/card' +import { Input } from '@amaxa/ui/input' +import { Label } from '@amaxa/ui/label' +import Link from 'next/link' +import { env } from '~/env' +import React from 'react' + + +export default function LoginForm() { + return ( +
+ + + Login + + Enter your email below to login to your account + + + +
+ { + env.NODE_ENV === "development" ? ( + <> + +
{ + "use server" + await signIn("email", { + password: data.get("password"), + redirectTo: "/" + }) + }} + > +
+ + +
+ +
+ + ) : null + } + +
{ + "use server" + await signIn("google", { redirectTo: "/" }) + }} + > + +
+
+
+ Have question's about how we use your data?{" "} + + Read our privacy statement here + +
+
+
+
+ ) +} diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx index 5ff4631..9622e86 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/layout.tsx @@ -1,7 +1,15 @@ import React, { cache } from "react"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { House, User, Search, Settings, Workflow, ChartArea } from "lucide-react"; +import { + ChartArea, + House, + Search, + Settings, + User, + Workflow, +} from "lucide-react"; + import { db } from "@amaxa/db/client"; import { Breadcrumb, diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx new file mode 100644 index 0000000..72fdd60 --- /dev/null +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx @@ -0,0 +1,84 @@ +"use client" +import { Button } from '@amaxa/ui/button' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@amaxa/ui/dialog' +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import React from 'react' +import { z } from 'zod' + +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@amaxa/ui/form" +import { Input } from "@amaxa/ui/input" + +export const addUserSchema = z.object({ + userId: z.string(), + projectId: z.string(), + permissions: z.array(z.string()), +}) +const schema = addUserSchema.omit({ + projectId: true, +}) + +type AddUserForm = z.infer + + +export default function AddUserForm({ + userMap, +}: { + userMap: string +}) { + + const form = useForm({ + resolver: zodResolver(schema), + }) + + function onSubmit(data: AddUserForm) { + } + + return ( + + + + + + + + Add A User + + +
+
+ + + ( + + User + + + + + The user you want to add to this project + + + + )} + /> + + + +
+
+
+ ) +} + diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx new file mode 100644 index 0000000..565c5ff --- /dev/null +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx @@ -0,0 +1,13 @@ +import { api } from "~/trpc/server"; +import AddUserForm from "./add-user-form"; + + +export async function AddUser() { + const userMap = api.projects + + return ( +
+ +
+ ) +} diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx index a5d8979..683cc00 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx @@ -46,7 +46,6 @@ export default function Page({

Permissions

-
diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/layout.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/layout.tsx index e69de29..1483f19 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/layout.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(tasks)/layout.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return
{children}
; +} diff --git a/apps/nextjs/src/app/api/project/[id]/route.ts b/apps/nextjs/src/app/api/project/[id]/route.ts index 11339e7..3398c59 100644 --- a/apps/nextjs/src/app/api/project/[id]/route.ts +++ b/apps/nextjs/src/app/api/project/[id]/route.ts @@ -1,7 +1,63 @@ -//TODO: the ablility for our landing site to fetch project data +import { and, eq } from "@amaxa/db"; +import { db } from "@amaxa/db/client"; +import { project_tracker, Projects, User } from "@amaxa/db/schema"; +import { NextApiRequest } from "next"; +import { NextResponse } from "next/server"; -export const GET = async () => { - // find the data - //TODO: find the project with this id, do a join and group the users by their roles, and parse that into an object then return it as json (users must have the isPublic option set to true) - // return: { project: { name: string, users: { [role: string]: User[] }, coaches: { [role: string]: User } } } -}; +export async function GET(req: NextApiRequest) { + try { + const { id } = req.query + + if (!id) { + return NextResponse.json({ error: "Project ID is required" }, { status: 400 }); + } + + const projectId = String(id); + + const result = await db.transaction(async (tx) => { + const project = await tx.query.Projects.findFirst({ + where: eq(Projects.id, projectId) + }); + + if (!project) { + return null; + } + + const users = await tx.select({ + id: User.id, + name: User.name, + role: User.role, + image: User.image + }).from(project_tracker) + .where(and(eq(project_tracker.projectId, projectId))) + .innerJoin(User, eq(project_tracker.userId, User.id)) + .innerJoin(Projects, eq(project_tracker.projectId, Projects.id)); + + const coaches = await tx.select({ + id: User.id, + name: User.name, + role: User.role, + image: User.image + }).from(project_tracker) + .where(and(eq(project_tracker.projectId, projectId))) + .innerJoin(User, eq(project_tracker.userId, User.id)) + .innerJoin(Projects, eq(project_tracker.projectId, Projects.id)); + + return { project, users, coaches }; + }); + + if (!result) { + return NextResponse.json({ error: "Project not found" }, { status: 404 }); + } + + return NextResponse.json({ + ...result.project, + users: result.users, + coaches: result.coaches + }); + + } catch (error) { + console.error("Error fetching project data:", error); + return NextResponse.json({ error: "Internal Server Error" }, { status: 500 }); + } +} diff --git a/packages/api/src/root.ts b/packages/api/src/root.ts index 11dacbd..04f973c 100644 --- a/packages/api/src/root.ts +++ b/packages/api/src/root.ts @@ -1,11 +1,13 @@ import { authRouter } from "./router/auth"; import { projectsRouter } from "./router/projects"; import { tasksRouter } from "./router/tasks"; +import { userRouter } from "./router/users"; import { createTRPCRouter } from "./trpc"; export const appRouter = createTRPCRouter({ auth: authRouter, projects: projectsRouter, + users: userRouter, tasks: tasksRouter, }); diff --git a/packages/api/src/router/users.ts b/packages/api/src/router/users.ts new file mode 100644 index 0000000..198d5c3 --- /dev/null +++ b/packages/api/src/router/users.ts @@ -0,0 +1,32 @@ +import { TRPCRouterRecord } from "@trpc/server"; +import { createTRPCRouter, protectedProcedure } from "../trpc"; +import { z } from "zod"; +import { project_tracker, User } from "@amaxa/db/schema"; +import { and, eq, isNull } from "@amaxa/db"; + + + +export const userRouter = createTRPCRouter({ + usersNotInProject: protectedProcedure + .input(z.object({ + projectId: z.string(), + })) + .mutation(async ({ ctx, input }) => { + const { projectId } = input; + + return await ctx.db + .select({ + id: User.id, + name: User.name, + email: User.email, + }) + .from(User) + .leftJoin(project_tracker, eq(User.id, project_tracker.userId)) + .where( + and( + eq(project_tracker.projectId, projectId), + isNull(project_tracker.userId) + ) + ); + }) +}) diff --git a/packages/auth/src/actions.ts b/packages/auth/src/actions.ts new file mode 100644 index 0000000..5937ad3 --- /dev/null +++ b/packages/auth/src/actions.ts @@ -0,0 +1,48 @@ +import { eq, sql } from "drizzle-orm"; + +import { db } from "@amaxa/db/client"; +import { project_tracker, ProjectPermission, User } from "@amaxa/db/schema"; + +const preparedGetUserInfo = db + .select({ + status: User.status, + role: User.role, + }) + .from(User) + .where(eq(User.id, sql.placeholder("id"))) + .prepare("getUserInfo"); + +const preparedGetUserProjectTrackers = db + .select({ + projectId: project_tracker.projectId, + permission: project_tracker.permission, + createdAt: project_tracker.createdAt, + }) + .from(project_tracker) + .where(eq(project_tracker.userId, sql.placeholder("userId"))) + .prepare("getUserProjectTrackers"); + +async function getUserInformation(id: string) { + const userData = await preparedGetUserInfo.execute({ id }); + const user = userData[0] || { + status: "Unverified", + role: "Student", + }; + + const projectTrackersData = await preparedGetUserProjectTrackers.execute({ + userId: id, + }); + + const project_permissions: Record = {}; + + for (const tracker of projectTrackersData) { + project_permissions[tracker.projectId] = tracker.permission; + } + + return { + ...user, + project_permissions, + }; +} + +export { getUserInformation }; diff --git a/packages/auth/src/config.ts b/packages/auth/src/config.ts index 12f6279..7069ddd 100644 --- a/packages/auth/src/config.ts +++ b/packages/auth/src/config.ts @@ -9,16 +9,24 @@ import { DrizzleAdapter } from "@auth/drizzle-adapter"; import Google from "next-auth/providers/google"; import { db } from "@amaxa/db/client"; -import { Account, Session, User, UserRoleEnum, UserStatusEnum } from "@amaxa/db/schema"; +import { + Account, + ProjectPermission, + Session, + User, + UserRole, + UserStatus, +} from "@amaxa/db/schema"; import { env } from "../env"; +import { getUserInformation } from "./actions"; declare module "next-auth" { interface Session { user: { - role: typeof UserRoleEnum; - project_permissions?: string; - status: typeof UserStatusEnum; + role: UserRole; + project_permissions?: Record; + status: UserStatus; id: string; } & DefaultSession["user"]; } @@ -43,24 +51,20 @@ export const authConfig = { : {}), secret: env.AUTH_SECRET, providers: [Google], + pages: { + signIn: "/auth/sign-in", + signOut: "/auth/sign-out", + }, callbacks: { session: async (opts) => { if (!("user" in opts)) throw new Error("unreachable with session strategy"); - const data = await db.query.User.findFirst({ - columns: { - status: true, - role: true, - } - }) return { ...opts.session, user: { ...opts.session.user, id: opts.user.id, - status: data?.status, - role: data?.role, }, }; }, @@ -73,23 +77,20 @@ export const validateToken = async ( const sessionToken = token.slice("Bearer ".length); const session = await adapter.getSessionAndUser?.(sessionToken); - const data = await db.query.User.findFirst({ - where: (User, { eq }) => eq(User.id, session?.user.id!), - columns: { - status: true, - role: true, - } - }) - return session - ? { + if (session) { + const data = await getUserInformation(session.user.id); + return { user: { ...session.user, - role: data?.role! as unknown as typeof UserRoleEnum, - status: data?.status! as unknown as typeof UserStatusEnum, + role: data.role, + status: data.status, + project_permissions: data.project_permissions, }, expires: session.session.expires.toISOString(), - } - : null; + }; + } else { + return null; + } }; export const invalidateSessionToken = async (token: string) => { diff --git a/packages/auth/src/permissions.ts b/packages/auth/src/permissions.ts index d21fd49..6d0abce 100644 --- a/packages/auth/src/permissions.ts +++ b/packages/auth/src/permissions.ts @@ -12,8 +12,8 @@ export const TEST_USER: E2EUsers = { user: { id: "test_user", name: "Jane Doe", - permissions: new Set(["basics"]), - project_permissions: "", + role: "Student", + status: "Unverified", }, // expires in +1 day expires: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), @@ -26,7 +26,8 @@ export const TEST_ADMIN: E2EUsers = { user: { id: "test_user", name: "test user", - permissions: new Set(["activities"]), + role: "Admin", + status: "Verified", }, expires: new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(), }, diff --git a/packages/db/src/perms.ts b/packages/db/src/perms.ts new file mode 100644 index 0000000..9de2f18 --- /dev/null +++ b/packages/db/src/perms.ts @@ -0,0 +1,3 @@ +export const permissions = ["write:tasks", "write:permissions"] as const; + +export type Permission = (typeof permissions)[number]; diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 29591c8..08bbe96 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -12,6 +12,7 @@ import { varchar, } from "drizzle-orm/pg-core"; import { createInsertSchema, createSelectSchema } from "drizzle-zod"; +import { Permission } from "./perms"; export const User = pgTable("user", { id: text("id") @@ -23,10 +24,10 @@ export const User = pgTable("user", { isPublic: boolean("is_public").notNull().default(true), status: varchar("status", { length: 30, - enum: ["Verified", "Unverifed", "Pending"], + enum: ["Verified", "Unverified", "Pending"], }) .notNull() - .default("Unverifed"), + .default("Unverified"), role: varchar("role", { length: 30, enum: ["Admin", "Coach", "Student"] }) .notNull() .default("Student"), @@ -37,15 +38,9 @@ export const User = pgTable("user", { image: varchar("image", { length: 255 }), }); - - -export const UserSchema = createSelectSchema(User); -export const CreateUserSchema = createInsertSchema(User).omit({ id: true }); - -export const UserStatusEnum = User.status.enumValues; -export const UserRoleEnum = User.role.enumValues; - - +export type User = typeof User.$inferSelect; +export type UserStatus = User["status"]; +export type UserRole = User["role"]; export const UserRelations = relations(User, ({ many }) => ({ accounts: many(Account), @@ -214,3 +209,25 @@ export const createProjectSchema = createInsertSchema(Projects).omit({ export type CreateProjectSchema = z.infer; export const statusValues = tasks.status.enumValues; + +export const project_tracker = pgTable("project_tracker", { + userId: text("user_id").notNull(), + projectId: text("project_id").notNull(), + permission: text("permissions").array().notNull().$type(), + createdAt: timestamp("created_at") + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), +}, (table) => ({ + pk: primaryKey({ columns: [table.userId, table.projectId] }), +})); + + +export const projectTrackerRelations = relations(project_tracker, ({ one }) => ({ + project: one(Projects, { + fields: [project_tracker.projectId], + references: [Projects.id], + }), + user: one(User, { fields: [project_tracker.userId], references: [User.id] }), +})); +export type ProjectTracker = typeof project_tracker.$inferSelect; +export type ProjectPermission = ProjectTracker["permission"]; diff --git a/packages/validators/src/index.ts b/packages/validators/src/index.ts index 4fb967c..fe5244f 100644 --- a/packages/validators/src/index.ts +++ b/packages/validators/src/index.ts @@ -6,3 +6,10 @@ export const unused = z.string().describe( with back and frontend, you can put them in here `, ); + + +export const addUserSchema = z.object({ + userId: z.string(), + projectId: z.string(), + permissions: z.array(z.string()), +}) From f803544e8500beeb51376c8699e6a6a02a7cd92c Mon Sep 17 00:00:00 2001 From: xiften <108333567+not-ani@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:14:55 -0600 Subject: [PATCH 2/7] feat: base before demo --- apps/nextjs/package.json | 1 + .../permissions/_components/add-user-form.tsx | 76 +++---- .../permissions/_components/add-user.tsx | 16 +- .../project/[id]/(root)/permissions/page.tsx | 2 + .../app/(home)/_components/sidebar-items.tsx | 91 ++++++++ .../src/app/(home)/_components/sidebar.tsx | 64 ++++++ .../app/(home)/action-guides/[id]/page.tsx | 25 +++ .../src/app/(home)/action-guides/com.tsx | 19 ++ .../action-guides/filters/PickerForm.tsx | 85 ++++++++ .../(home)/action-guides/filters/project.tsx | 18 ++ .../(home)/action-guides/filters/skill.tsx | 18 ++ .../src/app/(home)/action-guides/page.tsx | 73 +++++++ .../(home)/events/_components/CreateEvent.tsx | 157 ++++++++++++++ .../(home)/events/_components/EventCard.tsx | 40 ++++ .../(home)/events/_components/NoEvents.tsx | 24 +++ apps/nextjs/src/app/(home)/events/page.tsx | 60 ++++++ apps/nextjs/src/app/(home)/layout.tsx | 8 +- apps/nextjs/src/lib/nav.tsx | 25 +++ packages/api/src/root.ts | 2 + packages/api/src/router/events.ts | 26 +++ packages/api/src/router/tasks.ts | 2 +- packages/api/src/router/users.ts | 22 +- packages/db/src/schema.ts | 36 ++++ packages/ui/package.json | 2 + packages/ui/src/calendar.tsx | 72 +++++++ packages/ui/src/combobox.tsx | 65 ++---- .../ui/src/time-picker/time-picker-demo.tsx | 63 ++++++ .../ui/src/time-picker/time-picker-input.tsx | 131 +++++++++++ .../ui/src/time-picker/time-picker-utils.tsx | 203 ++++++++++++++++++ pnpm-lock.yaml | 23 ++ 30 files changed, 1341 insertions(+), 108 deletions(-) create mode 100644 apps/nextjs/src/app/(home)/_components/sidebar-items.tsx create mode 100644 apps/nextjs/src/app/(home)/_components/sidebar.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/[id]/page.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/com.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/filters/PickerForm.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/filters/project.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/filters/skill.tsx create mode 100644 apps/nextjs/src/app/(home)/action-guides/page.tsx create mode 100644 apps/nextjs/src/app/(home)/events/_components/CreateEvent.tsx create mode 100644 apps/nextjs/src/app/(home)/events/_components/EventCard.tsx create mode 100644 apps/nextjs/src/app/(home)/events/_components/NoEvents.tsx create mode 100644 apps/nextjs/src/app/(home)/events/page.tsx create mode 100644 apps/nextjs/src/lib/nav.tsx create mode 100644 packages/api/src/router/events.ts create mode 100644 packages/ui/src/calendar.tsx create mode 100644 packages/ui/src/time-picker/time-picker-demo.tsx create mode 100644 packages/ui/src/time-picker/time-picker-input.tsx create mode 100644 packages/ui/src/time-picker/time-picker-utils.tsx diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index a9cd739..72d301f 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -29,6 +29,7 @@ "@trpc/react-query": "11.0.0-rc.364", "@trpc/server": "11.0.0-rc.364", "@xyflow/react": "^12.0.3", + "date-fns": "^3.6.0", "geist": "^1.3.0", "next": "^14.2.3", "react": "18.3.1", diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx index 72fdd60..e2b9ba9 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx @@ -1,11 +1,9 @@ "use client" -import { Button } from '@amaxa/ui/button' +import React from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@amaxa/ui/dialog' import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" -import React from 'react' import { z } from 'zod' - import { Form, FormControl, @@ -15,31 +13,30 @@ import { FormLabel, FormMessage, } from "@amaxa/ui/form" -import { Input } from "@amaxa/ui/input" +import { Combobox } from '@amaxa/ui/combobox' +import { Button } from '@amaxa/ui/button' -export const addUserSchema = z.object({ +const addUserSchema = z.object({ userId: z.string(), - projectId: z.string(), permissions: z.array(z.string()), }) -const schema = addUserSchema.omit({ - projectId: true, -}) - -type AddUserForm = z.infer +type AddUserForm = z.infer export default function AddUserForm({ userMap, }: { - userMap: string + userMap: { + value: string; + label: string; + }[] }) { - const form = useForm({ - resolver: zodResolver(schema), + resolver: zodResolver(addUserSchema), }) function onSubmit(data: AddUserForm) { + console.log(data) } return ( @@ -49,36 +46,31 @@ export default function AddUserForm({ - - Add A User - + Add A User -
-
- - - ( - - User - - - - - The user you want to add to this project - - - - )} - /> - - - -
+
+ + ( + + User + + + + Select a user to add to the project + + + )} + /> + + +
) } - diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx index 565c5ff..c5ef4cc 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx @@ -3,11 +3,23 @@ import AddUserForm from "./add-user-form"; export async function AddUser() { - const userMap = api.projects + const data = await api.users.usersNotInProject(); + console.log(data) + // take the result and map it to { + // value: id, + // label: name + // }[] + + const userMap = data.map((user) => { + return { + value: user.id, + label: user.name! + } + }) return (
- +
) } diff --git a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx index 683cc00..c075f06 100644 --- a/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/page.tsx @@ -33,6 +33,7 @@ import { } from "@amaxa/ui/table"; import PermissionsModal from "./_components/permissions-dialog"; +import { AddUser } from "./_components/add-user"; export default function Page({ params, @@ -46,6 +47,7 @@ export default function Page({

Permissions

+
diff --git a/apps/nextjs/src/app/(home)/_components/sidebar-items.tsx b/apps/nextjs/src/app/(home)/_components/sidebar-items.tsx new file mode 100644 index 0000000..cfcd7b7 --- /dev/null +++ b/apps/nextjs/src/app/(home)/_components/sidebar-items.tsx @@ -0,0 +1,91 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import type { LucideIcon } from "lucide-react"; +import { getLink, additionalLinks } from "~/lib/nav"; +import { cn } from "@amaxa/ui"; + + +export interface SidebarLink { + title: string; + href: string; + icon: LucideIcon; +} + +const SidebarItems = () => { + const defaultLinks = getLink() + return ( + <> + + {additionalLinks.length > 0 + ? additionalLinks.map((l) => ( + + )) + : null} + + ); +}; +export default SidebarItems; + +const SidebarLinkGroup = ({ + links, + title, + border, +}: { + links: SidebarLink[]; + title?: string; + border?: boolean; +}) => { + const fullPathname = usePathname(); + const pathname = "/" + fullPathname.split("/")[1]; + + return ( +
+ {title ? ( +

+ {title} +

+ ) : null} +
    + {links.map((link) => ( +
  • + +
  • + ))} +
+
+ ); +}; +const SidebarLink = ({ + link, + active, +}: { + link: SidebarLink; + active: boolean; +}) => { + return ( + +
+
+ + {link.title} +
+ + ); +} diff --git a/apps/nextjs/src/app/(home)/_components/sidebar.tsx b/apps/nextjs/src/app/(home)/_components/sidebar.tsx new file mode 100644 index 0000000..1ffe53c --- /dev/null +++ b/apps/nextjs/src/app/(home)/_components/sidebar.tsx @@ -0,0 +1,64 @@ +import Link from "next/link"; + +import SidebarItems from "./sidebar-items"; +import { Poppins } from 'next/font/google'; +import { memo } from "react"; +import { auth, Session } from "@amaxa/auth"; +import { Avatar, AvatarFallback } from "@amaxa/ui/avatar"; + +const poppins = Poppins({ + subsets: ['latin'], + display: 'swap', + variable: '--font-poppins', + weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] +}); + +const Sidebar = async () => { + const session = await auth(); + if (session === null) return null; + + return ( + + ); +}; + +export default memo(Sidebar); + +const UserDetails = ({ session }: { session: Session }) => { + if (session === null) return null; + const user = session.user; + + if (!user?.name || user.name.length == 0) return null; + + return ( + +
+
+

{user.name ?? "John Doe"}

+

+ {user.email ?? "john@doe.com"} +

+
+ + + {user.name + ? user.name + ?.split(" ") + .map((word) => word[0]!.toUpperCase()) + .join("") + : "~"} + + +
+ + ); +} diff --git a/apps/nextjs/src/app/(home)/action-guides/[id]/page.tsx b/apps/nextjs/src/app/(home)/action-guides/[id]/page.tsx new file mode 100644 index 0000000..262bdab --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/[id]/page.tsx @@ -0,0 +1,25 @@ +import { notFound } from 'next/navigation' +import React from 'react' +import { Com } from '../com' +import { db } from '@amaxa/db/client' + +export default async function Page(props: { + params: { + id: string + } +}) { + + const { id } = props.params + const data = await db.query.guides.findFirst({ + where: (actionGuides, { eq }) => eq(actionGuides.id, parseInt(id)) + }) + + if (!data) return notFound() + + return ( +
+ + +
+ ) +} diff --git a/apps/nextjs/src/app/(home)/action-guides/com.tsx b/apps/nextjs/src/app/(home)/action-guides/com.tsx new file mode 100644 index 0000000..bc19170 --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/com.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import "react-notion/src/styles.css"; +import { BlockMapType, NotionRenderer } from 'react-notion' + +export const Com = async (props: { + id: string +}) => { + const data = await fetch( + `https://notion-api.splitbee.io/v1/page/${props.id}` + ).then(res => res.json()) as BlockMapType + + return ( +
+ +
+ ) +} diff --git a/apps/nextjs/src/app/(home)/action-guides/filters/PickerForm.tsx b/apps/nextjs/src/app/(home)/action-guides/filters/PickerForm.tsx new file mode 100644 index 0000000..3bc1725 --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/filters/PickerForm.tsx @@ -0,0 +1,85 @@ +"use client"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; +import { CaretSortIcon } from "@radix-ui/react-icons"; +import { CheckIcon } from "lucide-react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import React from "react"; + +type CourseData = { + value: number; + label: string; +}; + +export function PickerForm(props: { data: CourseData[], idx: string }) { + const [open, setOpen] = React.useState(false); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const { data, idx } = props; + const [value, setValue] = React.useState(searchParams.get(idx) ?? ""); + + function handleChange(value: string) { + if (value == "" || value == " " || value == null || value === "clear") { + return router.push(pathname); + } + setValue(value); + router.push(`${pathname}/?${idx}=${value}`); + } + + return ( + + + + + + + + No Course found. + + {data.map((framework) => ( + { + handleChange(currentValue === value ? "" : currentValue); + setOpen(false); + }} + > + {framework.label ?? "Clear"} + + + ))} + + + + + ); +} diff --git a/apps/nextjs/src/app/(home)/action-guides/filters/project.tsx b/apps/nextjs/src/app/(home)/action-guides/filters/project.tsx new file mode 100644 index 0000000..163213c --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/filters/project.tsx @@ -0,0 +1,18 @@ +import { api } from "@/trpc/server"; +import { PickerForm } from "./PickerForm"; + + + +export const FilterProject = async () => { + const data = await api.actionGuides.projectNames.query(); + const courseData = data.map((course) => ({ + value: course.id, + label: course.name, + })); + + return ( +
+ +
+ ); +} diff --git a/apps/nextjs/src/app/(home)/action-guides/filters/skill.tsx b/apps/nextjs/src/app/(home)/action-guides/filters/skill.tsx new file mode 100644 index 0000000..a62589d --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/filters/skill.tsx @@ -0,0 +1,18 @@ +import React from 'react' +import { PickerForm } from './PickerForm'; +import { api } from '@/trpc/server'; + +export const FilterSkill = async () => { + const data = await api.actionGuides.skillNames.query(); + const courseData = data.map((course) => ({ + value: course.id, + label: course.name, + })); + + return ( +
+ +
+ ); + +} diff --git a/apps/nextjs/src/app/(home)/action-guides/page.tsx b/apps/nextjs/src/app/(home)/action-guides/page.tsx new file mode 100644 index 0000000..f5621a4 --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/page.tsx @@ -0,0 +1,73 @@ +import { unstable_noStore as noStore } from "next/cache"; +import Link from "next/link"; + +import { checkAuth, getUserAuth } from "@/server/auth"; +import { api } from "@/trpc/server"; +import { z } from "zod"; +import Search from "@/components/Search"; +import Image from "next/image" +import { Card, CardContent, CardFooter } from "@/components/ui/card"; +import { CreateProjects } from "@/components/CreateProjects"; +import { FilterProject } from "./filters/project"; +import { FilterSkill } from "./filters/skill"; +import { CreateActionGuide } from "@/components/CreateActionGuide"; + + +const searchParamsSchema = z.object({ + name: z.string().optional(), + projectId: z.string().optional(), + skillId: z.string().optional(), +}); + + +export default async function Home(props: { + + searchParams: Record; +}) { + noStore(); + await checkAuth() + const { session } = await getUserAuth() + const { name, projectId, skillId } = searchParamsSchema.parse(props.searchParams); + + + const data = await api.actionGuides.all.query({ + name: name, + projectId: projectId ? parseInt(projectId) : undefined, + skillId: skillId ? parseInt(skillId) : undefined + }) + return ( +
+
+ +
+ + + { + session?.user.permissions.includes("add:action_guide") ? ( +
+ +
+ ) : null + } +
+ +
+
+ {data.map((subject) => { + return ( + + + + {subject.name} Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat. + + + + + + ); + })} +
+
+ ); + +} diff --git a/apps/nextjs/src/app/(home)/events/_components/CreateEvent.tsx b/apps/nextjs/src/app/(home)/events/_components/CreateEvent.tsx new file mode 100644 index 0000000..9a8ce8a --- /dev/null +++ b/apps/nextjs/src/app/(home)/events/_components/CreateEvent.tsx @@ -0,0 +1,157 @@ +"use client" +import { Button } from '@amaxa/ui/button' +import { format } from "date-fns"; +import { zodResolver } from "@hookform/resolvers/zod" +import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@amaxa/ui/dialog' +import { TimePickerDemo } from "@amaxa/ui/time-picker/time-picker-demo" +import { Calendar } from "@amaxa/ui/calendar" +import { useForm } from 'react-hook-form' +import { Input } from '@amaxa/ui/input' +import { toast } from '@amaxa/ui/toast' +import { useRouter } from 'next/navigation' +import React from 'react' +import { showErrorToast } from '~/lib/handle-error' +import { api } from '~/trpc/react' +import { createEventSchema } from '@amaxa/db/schema' +import { z } from 'zod' +import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@amaxa/ui/form' +import { Textarea } from '@amaxa/ui/textarea' +import { Popover, PopoverContent, PopoverTrigger } from '@amaxa/ui/popover' +import { cn } from '@amaxa/ui' +import { CalendarIcon } from 'lucide-react' + +type CreateEventProps = z.infer + +export const CreateEvent = () => { + const router = useRouter() + + const { mutate: create } = api.events.create.useMutation({ + onSuccess: () => { + toast.success('Event created') + router.refresh() + }, + onError: (error) => { + showErrorToast(error) + }, + }) + + const form = useForm({ + resolver: zodResolver(createEventSchema) + }) + + function handleSubmit(data: CreateEventProps) { + create(data) + } + + return ( +
+ + + + + + + Create Event + + Here you can create a new event + + +
+
+ + ( + + + Event Name + + + + + + THe name of the event + + + + )} + /> + + ( + + Time + + + + + + + + +
+ +
+
+
+ + The time of the event + + +
+ )} + /> + ( + + + Description + + +