From a779127247fd4951b11027aeb6ac539d8be528e6 Mon Sep 17 00:00:00 2001 From: SwiichyCode Date: Wed, 13 Mar 2024 21:40:29 +0100 Subject: [PATCH] feat: implement add ressource modal / form --- .../{repositories => (sharing)}/layout.tsx | 0 .../repositories/[repositoryId]/layout.tsx | 0 .../repositories/[repositoryId]/loading.tsx | 0 .../repositories/[repositoryId]/page.tsx | 0 .../{ => (sharing)}/repositories/loading.tsx | 0 .../{ => (sharing)}/repositories/page.tsx | 0 src/app/(app)/(sharing)/resources/loading.tsx | 11 +++ src/app/(app)/(sharing)/resources/page.tsx | 9 +++ src/components/layouts/Header.tsx | 7 +- src/components/layouts/SharingBtn.tsx | 51 ++++++++++++++ .../layouts/Sidebar/SidebarNavigation.tsx | 12 +++- src/components/ui/cta-link.tsx | 2 +- src/components/ui/select-form.tsx | 59 ++++++++++++++++ src/components/ui/select.tsx | 4 +- src/config/constants/index.ts | 1 + src/config/env.js | 2 + src/middleware.ts | 15 +++- .../components/RepositoryInputSearch.tsx | 8 ++- .../components/RepositoryShareBtn.tsx | 30 -------- .../components/_forms/add-repository-form.tsx | 9 +-- .../components/_forms/add-resource-form.tsx | 69 +++++++++++++++++++ .../components/_forms/add-resource-schema.ts | 7 ++ .../stores/useShareResourcesModal.ts | 11 +++ 23 files changed, 262 insertions(+), 45 deletions(-) rename src/app/(app)/{repositories => (sharing)}/layout.tsx (100%) rename src/app/(app)/{ => (sharing)}/repositories/[repositoryId]/layout.tsx (100%) rename src/app/(app)/{ => (sharing)}/repositories/[repositoryId]/loading.tsx (100%) rename src/app/(app)/{ => (sharing)}/repositories/[repositoryId]/page.tsx (100%) rename src/app/(app)/{ => (sharing)}/repositories/loading.tsx (100%) rename src/app/(app)/{ => (sharing)}/repositories/page.tsx (100%) create mode 100644 src/app/(app)/(sharing)/resources/loading.tsx create mode 100644 src/app/(app)/(sharing)/resources/page.tsx create mode 100644 src/components/layouts/SharingBtn.tsx create mode 100644 src/components/ui/select-form.tsx delete mode 100644 src/modules/repositories/components/RepositoryShareBtn.tsx create mode 100644 src/modules/resources/components/_forms/add-resource-form.tsx create mode 100644 src/modules/resources/components/_forms/add-resource-schema.ts create mode 100644 src/modules/resources/stores/useShareResourcesModal.ts diff --git a/src/app/(app)/repositories/layout.tsx b/src/app/(app)/(sharing)/layout.tsx similarity index 100% rename from src/app/(app)/repositories/layout.tsx rename to src/app/(app)/(sharing)/layout.tsx diff --git a/src/app/(app)/repositories/[repositoryId]/layout.tsx b/src/app/(app)/(sharing)/repositories/[repositoryId]/layout.tsx similarity index 100% rename from src/app/(app)/repositories/[repositoryId]/layout.tsx rename to src/app/(app)/(sharing)/repositories/[repositoryId]/layout.tsx diff --git a/src/app/(app)/repositories/[repositoryId]/loading.tsx b/src/app/(app)/(sharing)/repositories/[repositoryId]/loading.tsx similarity index 100% rename from src/app/(app)/repositories/[repositoryId]/loading.tsx rename to src/app/(app)/(sharing)/repositories/[repositoryId]/loading.tsx diff --git a/src/app/(app)/repositories/[repositoryId]/page.tsx b/src/app/(app)/(sharing)/repositories/[repositoryId]/page.tsx similarity index 100% rename from src/app/(app)/repositories/[repositoryId]/page.tsx rename to src/app/(app)/(sharing)/repositories/[repositoryId]/page.tsx diff --git a/src/app/(app)/repositories/loading.tsx b/src/app/(app)/(sharing)/repositories/loading.tsx similarity index 100% rename from src/app/(app)/repositories/loading.tsx rename to src/app/(app)/(sharing)/repositories/loading.tsx diff --git a/src/app/(app)/repositories/page.tsx b/src/app/(app)/(sharing)/repositories/page.tsx similarity index 100% rename from src/app/(app)/repositories/page.tsx rename to src/app/(app)/(sharing)/repositories/page.tsx diff --git a/src/app/(app)/(sharing)/resources/loading.tsx b/src/app/(app)/(sharing)/resources/loading.tsx new file mode 100644 index 0000000..0b2b18c --- /dev/null +++ b/src/app/(app)/(sharing)/resources/loading.tsx @@ -0,0 +1,11 @@ +import { RepositoryCardSkeleton } from "@/modules/repositories/components/RepositoryCard/RepositoryCardSkeleton"; + +export default function loading() { + return ( +
+ {Array.from({ length: 15 }).map((_, index) => ( + + ))} +
+ ); +} diff --git a/src/app/(app)/(sharing)/resources/page.tsx b/src/app/(app)/(sharing)/resources/page.tsx new file mode 100644 index 0000000..4de9c35 --- /dev/null +++ b/src/app/(app)/(sharing)/resources/page.tsx @@ -0,0 +1,9 @@ +import { AddResourceForm } from "@/modules/resources/components/_forms/add-resource-form"; + +export default async function ResourcesPage() { + return ( + <> + + + ); +} diff --git a/src/components/layouts/Header.tsx b/src/components/layouts/Header.tsx index 7161ac6..5dc10bf 100644 --- a/src/components/layouts/Header.tsx +++ b/src/components/layouts/Header.tsx @@ -1,12 +1,11 @@ +import type { PropsWithChildren } from "react"; import { getServerAuthSession } from "@/config/server/auth"; import { Logo } from "@/components/layouts/Logo"; import { GithubLink } from "@/components/ui/github-link"; import { RepositoryInputSearch } from "@/modules/repositories/components/RepositoryInputSearch"; -import { RepositoryShareBtn } from "@/modules/repositories/components/RepositoryShareBtn"; +import { SharingBtn } from "@/components/layouts/SharingBtn"; import { AuthNavigation } from "@/components/layouts/AuthNavigation"; - import { URL } from "@/config/constants"; -import type { PropsWithChildren } from "react"; export const HeaderLayout = ({ children }: PropsWithChildren) => { return ( @@ -27,7 +26,7 @@ export const Header = async () => {
- +
diff --git a/src/components/layouts/SharingBtn.tsx b/src/components/layouts/SharingBtn.tsx new file mode 100644 index 0000000..b9930b1 --- /dev/null +++ b/src/components/layouts/SharingBtn.tsx @@ -0,0 +1,51 @@ +"use client"; +import { usePathname } from "next/navigation"; +import { signIn } from "next-auth/react"; +import { Button } from "@/components/ui/button"; +import { useShareRepositoryModal } from "@/modules/repositories/stores/useShareRepositoryModal"; +import { useShareResourceModal } from "@/modules/resources/stores/useShareResourcesModal"; +import { RepoIcon, FileDirectoryIcon } from "@primer/octicons-react"; +import { URL } from "@/config/constants"; +import type { Session } from "next-auth"; + +type Props = { + session: Session | null; +}; + +export const SharingBtn = ({ session }: Props) => { + const { setOpen: setOpenShareRepositoryModal } = useShareRepositoryModal(); + const { setOpen: setOpenShareResourceModal } = useShareResourceModal(); + const pathname = usePathname(); + + const pageActions = { + [URL.REPOSITORIES]: { + action: setOpenShareRepositoryModal, + label: "Share Repository", + icon: RepoIcon, + }, + [URL.RESOURCES]: { + action: setOpenShareResourceModal, + label: "Share Resource", + icon: FileDirectoryIcon, + }, + }; + + const currentPageAction = pageActions[pathname]; + + if (!currentPageAction) return null; + + const handleOpen = async () => { + if (session) { + currentPageAction.action(true); + } else { + await signIn("github"); + } + }; + + return ( + + ); +}; diff --git a/src/components/layouts/Sidebar/SidebarNavigation.tsx b/src/components/layouts/Sidebar/SidebarNavigation.tsx index 4165318..9c6fc80 100644 --- a/src/components/layouts/Sidebar/SidebarNavigation.tsx +++ b/src/components/layouts/Sidebar/SidebarNavigation.tsx @@ -1,4 +1,9 @@ -import { GearIcon, PersonIcon, RepoIcon } from "@primer/octicons-react"; +import { + GearIcon, + PersonIcon, + RepoIcon, + FileDirectoryIcon, +} from "@primer/octicons-react"; import { Separator } from "@/components/ui/separator"; import { SidebarNavigationLink } from "./SidebarNavigationLink"; import { SidebarSignout } from "./SidebarSignout"; @@ -21,6 +26,11 @@ const SidebarNavigationItems = [ href: URL.REPOSITORIES, icon: RepoIcon, }, + { + name: "Resources", + href: URL.RESOURCES, + icon: FileDirectoryIcon, + }, { name: "Settings", href: URL.SETTINGS, diff --git a/src/components/ui/cta-link.tsx b/src/components/ui/cta-link.tsx index f2b6b44..2122f7f 100644 --- a/src/components/ui/cta-link.tsx +++ b/src/components/ui/cta-link.tsx @@ -9,7 +9,7 @@ type Props = { export const CTALink = ({ url, fn }: Props) => { return ( diff --git a/src/components/ui/select-form.tsx b/src/components/ui/select-form.tsx new file mode 100644 index 0000000..d8a703e --- /dev/null +++ b/src/components/ui/select-form.tsx @@ -0,0 +1,59 @@ +"use client"; +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@/components/ui/select"; + +interface SelectProps extends React.ComponentPropsWithoutRef<"select"> { + control: any; + name: string; + placeholder: string; + items: string[]; + label: string; +} + +export function SelectForm({ + control, + name, + placeholder, + label, + ...props +}: SelectProps) { + return ( + ( + + {label} + + + + + )} + /> + ); +} diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index d370b2a..58989ff 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm capitalize ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className, )} {...props} @@ -118,7 +118,7 @@ const SelectItem = React.forwardRef< { + const sessionToken = req.cookies.get(env.SESSION_TOKEN_NAME); + if (!sessionToken) return false; + + return true; + }, + }, + secret: env.NEXTAUTH_SECRET, +}); export const config = { matcher: ["/profile", "/settings"] }; diff --git a/src/modules/repositories/components/RepositoryInputSearch.tsx b/src/modules/repositories/components/RepositoryInputSearch.tsx index ecdebf6..d19cff2 100644 --- a/src/modules/repositories/components/RepositoryInputSearch.tsx +++ b/src/modules/repositories/components/RepositoryInputSearch.tsx @@ -1,5 +1,6 @@ "use client"; import { useTransition, useRef, useState } from "react"; +import { usePathname } from "next/navigation"; import { parseAsString, useQueryState } from "nuqs"; import { useMediaQuery, useDebounceCallback } from "usehooks-ts"; import { Drawer, DrawerContent } from "@/components/ui/drawer"; @@ -7,6 +8,7 @@ import { Input } from "@/components/ui/input"; import { Spinner } from "@/components/ui/spinner"; import { SearchIcon } from "@primer/octicons-react"; import { cn } from "@/lib/utils"; +import { URL } from "@/config/constants"; type Props = { placeholder?: string; @@ -20,7 +22,7 @@ export const RepositoryInputSearch = ({ placeholder }: Props) => { "query", parseAsString.withDefault("").withOptions({ startTransition }), ); - + const pathname = usePathname(); const inputRef = useRef(null); const isDesktop = useMediaQuery("(min-width: 1024px)"); @@ -33,10 +35,12 @@ export const RepositoryInputSearch = ({ placeholder }: Props) => { 300, ); + if (pathname !== URL.REPOSITORIES) return null; + return (
{ - const { setOpen } = useShareRepositoryModal(); - const pathname = usePathname(); - - if (pathname !== URL.REPOSITORIES) return null; - - const handleOpen = async () => { - session ? setOpen(true) : await signIn("github"); - }; - - return ( - - ); -}; diff --git a/src/modules/repositories/components/_forms/add-repository-form.tsx b/src/modules/repositories/components/_forms/add-repository-form.tsx index 1e652ba..e69e8fe 100644 --- a/src/modules/repositories/components/_forms/add-repository-form.tsx +++ b/src/modules/repositories/components/_forms/add-repository-form.tsx @@ -3,16 +3,17 @@ import { useTransition } from "react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; -import { Form } from "@/components/ui/form"; -import { formRepositorySchema } from "./add-repository-schema"; -import { useToast } from "@/components/ui/use-toast"; +import { useFetchInfiniteRepositories } from "@/modules/repositories/hooks/use-fetch-infinite-repositories"; import { useQueryParamsContext } from "@/modules/repositories/context/queryParamsContext"; import { useShareRepositoryModal } from "@/modules/repositories/stores/useShareRepositoryModal"; +import { useToast } from "@/components/ui/use-toast"; +import { Form } from "@/components/ui/form"; +import { formRepositorySchema } from "./add-repository-schema"; import { Dialog, DialogContent } from "@/components/ui/dialog"; import { InputForm } from "@/components/ui/input-form"; import { RichTextFieldForm } from "@/components/ui/rich-textfield-form"; import { SubmitButton } from "@/components/ui/submit-button"; -import { useFetchInfiniteRepositories } from "@/modules/repositories/hooks/use-fetch-infinite-repositories"; + import { postRepositoryAction } from "@/services/actions/post-repository"; import type * as z from "zod"; diff --git a/src/modules/resources/components/_forms/add-resource-form.tsx b/src/modules/resources/components/_forms/add-resource-form.tsx new file mode 100644 index 0000000..3fa2df5 --- /dev/null +++ b/src/modules/resources/components/_forms/add-resource-form.tsx @@ -0,0 +1,69 @@ +"use client"; +import { useTransition } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { useShareResourceModal } from "@/modules/resources/stores/useShareResourcesModal"; +import { formAddResourceSchema } from "./add-resource-schema"; +import { Form } from "@/components/ui/form"; +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { InputForm } from "@/components/ui/input-form"; +import { RichTextFieldForm } from "@/components/ui/rich-textfield-form"; +import { SelectForm } from "@/components/ui/select-form"; +import { SubmitButton } from "@/components/ui/submit-button"; +import type * as z from "zod"; +import { TextAreaForm } from "@/components/ui/text-area-form"; + +export const AddResourceForm = () => { + const [isPending, startTransition] = useTransition(); + const { open, setOpen } = useShareResourceModal(); + + const form = useForm>({ + resolver: zodResolver(formAddResourceSchema), + defaultValues: { + url: "", + description: "", + type: "article", + }, + }); + + function onSubmit(data: z.infer) { + startTransition(async () => { + console.log(data); + }); + + form.reset(); + } + + return ( + + +
+ + + + + + + Share + + +
+
+ ); +}; diff --git a/src/modules/resources/components/_forms/add-resource-schema.ts b/src/modules/resources/components/_forms/add-resource-schema.ts new file mode 100644 index 0000000..2e787f8 --- /dev/null +++ b/src/modules/resources/components/_forms/add-resource-schema.ts @@ -0,0 +1,7 @@ +import * as z from "zod"; + +export const formAddResourceSchema = z.object({ + url: z.string().url(), + description: z.string().nullable(), + type: z.enum(["article", "video", "book", "course"]), +}); diff --git a/src/modules/resources/stores/useShareResourcesModal.ts b/src/modules/resources/stores/useShareResourcesModal.ts new file mode 100644 index 0000000..10dc0fa --- /dev/null +++ b/src/modules/resources/stores/useShareResourcesModal.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +interface ShareResourceModalState { + open: boolean; + setOpen: (open: boolean) => void; +} + +export const useShareResourceModal = create((set) => ({ + open: false, + setOpen: (open) => set({ open }), +}));