diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 8dcdf0a..b96fa43 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", @@ -29,10 +29,12 @@ "@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", "react-dom": "18.3.1", + "react-notion": "^0.10.0", "reactflow": "^11.11.4", "recharts": "^2.12.7", "superjson": "2.2.1", 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..0f04bc4 --- /dev/null +++ b/apps/nextjs/src/app/(auth)/sign-in/page.tsx @@ -0,0 +1,72 @@ +import Link from "next/link"; + +import { signIn } from "@amaxa/auth"; +import { Button } from "@amaxa/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@amaxa/ui/card"; +import { Input } from "@amaxa/ui/input"; +import { Label } from "@amaxa/ui/label"; + +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..5691f40 --- /dev/null +++ b/apps/nextjs/src/app/(auth)/sign-out/page.tsx @@ -0,0 +1,73 @@ +import React from "react"; +import Link from "next/link"; + +import { signIn } from "@amaxa/auth"; +import { Button } from "@amaxa/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@amaxa/ui/card"; +import { Input } from "@amaxa/ui/input"; +import { Label } from "@amaxa/ui/label"; + +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/(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..114faa5 --- /dev/null +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user-form.tsx @@ -0,0 +1,83 @@ +"use client"; + +import React from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "@amaxa/ui/button"; +import { Combobox } from "@amaxa/ui/combobox"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@amaxa/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@amaxa/ui/form"; + +const addUserSchema = z.object({ + userId: z.string(), + permissions: z.array(z.string()), +}); + +type AddUserForm = z.infer; + +export default function AddUserForm({ + userMap, +}: { + userMap: { + value: string; + label: string; + }[]; +}) { + const form = useForm({ + resolver: zodResolver(addUserSchema), + }); + + function onSubmit(data: AddUserForm) { + console.log(data); + } + + return ( + + + + + + + Add A User + +
+ + ( + + 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 new file mode 100644 index 0000000..06c5203 --- /dev/null +++ b/apps/nextjs/src/app/(dashboard)/project/[id]/(root)/permissions/_components/add-user.tsx @@ -0,0 +1,19 @@ +import { api } from "~/trpc/server"; +import AddUserForm from "./add-user-form"; + +export async function AddUser() { + const data = await api.users.usersNotInProject(); + + const userMap = data.map((user) => { + return { + value: user.id, + label: user.name ?? "No Name User", + }; + }); + + 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..fb208f5 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 @@ -32,6 +32,7 @@ import { TableRow, } from "@amaxa/ui/table"; +import { AddUser } from "./_components/add-user"; import PermissionsModal from "./_components/permissions-dialog"; export default function Page({ @@ -46,7 +47,7 @@ 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/(home)/@modal/(...)guide/[id]/com.tsx b/apps/nextjs/src/app/(home)/@modal/(...)guide/[id]/com.tsx new file mode 100644 index 0000000..ec6ed37 --- /dev/null +++ b/apps/nextjs/src/app/(home)/@modal/(...)guide/[id]/com.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import "react-notion/src/styles.css"; + +import type { BlockMapType } from "react-notion"; +import { NotionRenderer } from "react-notion"; + +import { Card, CardContent, CardHeader, CardTitle } from "@amaxa/ui/card"; + +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 ( + + + Action Guide + + + + + + ); +}; diff --git a/apps/nextjs/src/app/(home)/@modal/(...)guide/[id]/page.tsx b/apps/nextjs/src/app/(home)/@modal/(...)guide/[id]/page.tsx new file mode 100644 index 0000000..e21eb9d --- /dev/null +++ b/apps/nextjs/src/app/(home)/@modal/(...)guide/[id]/page.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import Modal from "@amaxa/ui/modal"; + +import { Com } from "./com"; + +export default function Page(props: { + params: { + id: string; + }; +}) { + const { id } = props.params; + console.log(id); + + return ( + + + + ); +} diff --git a/apps/nextjs/src/app/(home)/@modal/default.tsx b/apps/nextjs/src/app/(home)/@modal/default.tsx new file mode 100644 index 0000000..6ddf1b7 --- /dev/null +++ b/apps/nextjs/src/app/(home)/@modal/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null; +} 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..ed8c919 --- /dev/null +++ b/apps/nextjs/src/app/(home)/_components/sidebar-items.tsx @@ -0,0 +1,92 @@ +"use client"; + +import type { LucideIcon } from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +import { cn } from "@amaxa/ui"; + +import { additionalLinks, getLink } from "~/lib/nav"; + +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..92dffa3 --- /dev/null +++ b/apps/nextjs/src/app/(home)/_components/sidebar.tsx @@ -0,0 +1,67 @@ +import { memo } from "react"; +import { Poppins } from "next/font/google"; +import Link from "next/link"; + +import type { Session } from "@amaxa/auth"; +import { auth } from "@amaxa/auth"; +import { Avatar, AvatarFallback } from "@amaxa/ui/avatar"; + +import SidebarItems from "./sidebar-items"; + +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 }) => { + 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/_components/GuideCard.tsx b/apps/nextjs/src/app/(home)/action-guides/_components/GuideCard.tsx new file mode 100644 index 0000000..a880c5e --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/_components/GuideCard.tsx @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +// @ts-nocheck + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +"use client"; + +import { useMemo, useState } from "react"; +import Link from "next/link"; + +import { Badge } from "@amaxa/ui/badge"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "@amaxa/ui/card"; +import { Input } from "@amaxa/ui/input"; + +export default function Guides() { + const [search, setSearch] = useState(""); + const [selectedTags, setSelectedTags] = useState([]); + const actionGuides = [ + { + id: 1, + title: "Test Action Guide 1", + description: + "Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", + tags: ["Fundraising"], + embedId: "Opening-Guide-Copy-7acb2eb37957405694b19afa43ae7b9c", + }, + { + id: 2, + title: "Test Action Guide 2", + description: + "Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", + tags: ["Impact Outreach"], + embedId: "Opening-Guide-Copy-7acb2eb37957405694b19afa43ae7b9c", + }, + ]; + + const filteredGuides = useMemo(() => { + return actionGuides.filter((guide) => { + const titleMatch = guide.title + .toLowerCase() + .includes(search.toLowerCase()); + const tagMatch = selectedTags.every((tag) => guide.tags.includes(tag)); + return titleMatch && tagMatch; + }); + }, [search, selectedTags]); + const handleSearch = (e: any) => { + setSearch(e.target.value); + }; + const handleTagClick = (tag: never) => { + if (selectedTags.includes(tag)) { + setSelectedTags(selectedTags.filter((t) => t !== tag)); + } else { + setSelectedTags([...selectedTags, tag]); + } + }; + const allTags = useMemo(() => { + return [...new Set(actionGuides.flatMap((guide) => guide.tags))]; + }, [actionGuides]); + + return ( +
+

Action Guides

+
+ +
+
+

Filter by Skill

+
+ {allTags.map((tag: string) => ( + handleTagClick(tag)} + className="cursor-pointer" + > + {tag} + + ))} +
+
+
+ {filteredGuides.map((guide) => ( + + + + {guide.title} + + +

{guide.description}

+
+ +
+ {guide.tags.map((tag) => ( + + {tag} + + ))} +
+
+
+ + ))} +
+
+ ); +} 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..32b9a27 --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/com.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +import "react-notion/src/styles.css"; + +import type { BlockMapType } from "react-notion"; +import { 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/page.tsx b/apps/nextjs/src/app/(home)/action-guides/page.tsx new file mode 100644 index 0000000..e7ff82f --- /dev/null +++ b/apps/nextjs/src/app/(home)/action-guides/page.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +import Guides from "./_components/GuideCard"; + +export default function Page() { + return ( +
+ +
+ ); +} 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..5dff99e --- /dev/null +++ b/apps/nextjs/src/app/(home)/events/_components/CreateEvent.tsx @@ -0,0 +1,246 @@ +"use client"; + +import type { z } from "zod"; +import React from "react"; +import { useRouter } from "next/navigation"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { format } from "date-fns"; +import { CalendarIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; + +import { createEventSchema } from "@amaxa/db/schema"; +import { cn } from "@amaxa/ui"; +import { Button } from "@amaxa/ui/button"; +import { Calendar } from "@amaxa/ui/calendar"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@amaxa/ui/dialog"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@amaxa/ui/form"; +import { Input } from "@amaxa/ui/input"; +import { Popover, PopoverContent, PopoverTrigger } from "@amaxa/ui/popover"; +import { Switch } from "@amaxa/ui/switch"; +import { Textarea } from "@amaxa/ui/textarea"; +import { TimePickerDemo } from "@amaxa/ui/time-picker/time-picker-demo"; +import { toast } from "@amaxa/ui/toast"; + +import { showErrorToast } from "~/lib/handle-error"; +import { api } from "~/trpc/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 + + + )} + /> + + ( + + Registration Link + + + + + Link for event registration + + + + )} + /> + ( + + Time + + + + + + + + +
+ +
+
+
+ The time of the event + +
+ )} + /> + ( + + Description + +