diff --git a/src/components/atomic/Toggle.tsx b/src/components/atomic/Toggle.tsx new file mode 100644 index 00000000..bf700826 --- /dev/null +++ b/src/components/atomic/Toggle.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { Switch } from "@headlessui/react"; + +interface ToggleProps { + enabled: boolean; + onClick(checked: boolean): void; +} + +const Toggle = (props: ToggleProps) => { + const { enabled, onClick } = props; + + return ( + + Enable notifications + + + ); +}; + +export default Toggle; diff --git a/src/components/atomic/index.tsx b/src/components/atomic/index.tsx index 9873f290..a1217732 100644 --- a/src/components/atomic/index.tsx +++ b/src/components/atomic/index.tsx @@ -7,3 +7,4 @@ export { default as Modal } from "./Modal"; export { default as Tag } from "./Tag"; export { default as Text } from "./Text"; export { default as TextArea } from "./TextArea"; +export { default as Toggle } from "./Toggle"; diff --git a/src/graphql/queries/getProgramBySlug.graphql b/src/graphql/queries/getProgramBySlug.graphql index 7af07c4d..23d96760 100644 --- a/src/graphql/queries/getProgramBySlug.graphql +++ b/src/graphql/queries/getProgramBySlug.graphql @@ -15,5 +15,6 @@ query getProgramBySlug($slug: String!) { name listIndex } + public } } diff --git a/src/layouts/ChooseTabLayout.tsx b/src/layouts/ChooseTabLayout.tsx index 53a004b7..1736e832 100644 --- a/src/layouts/ChooseTabLayout.tsx +++ b/src/layouts/ChooseTabLayout.tsx @@ -1,20 +1,17 @@ import { useRouter } from "next/router"; -import React, { Fragment } from "react"; +import React from "react"; import { AuthorizationLevel, useAuthorizationLevel } from "../hooks"; import { parseParam } from "../utils"; import { MAP_PROFILETYPE_TO_ROUTE } from "../utils/constants"; -import { AdminTabLayout, MenteeTabLayout, MentorTabLayout } from "./TabLayout"; +import { + AdminTabLayout, + MenteeTabLayout, + MentorTabLayout, + NotInProgramTabLayout, +} from "./TabLayout"; import NoMatchingProfileLayout from "./TabLayout/NoMatchingProfileTabLayout"; import { BaseTabLayoutProps } from "./TabLayout/TabLayout"; -const NotInProgramTabLayout: React.FC = ({ - children, - basePath, -}) => { - basePath; - return {children}; -}; - function getTabLayout( authLevel: AuthorizationLevel ): React.FC { diff --git a/src/layouts/TabLayout/NotInProgramTabLayout.tsx b/src/layouts/TabLayout/NotInProgramTabLayout.tsx new file mode 100644 index 00000000..0859f7b7 --- /dev/null +++ b/src/layouts/TabLayout/NotInProgramTabLayout.tsx @@ -0,0 +1,32 @@ +import { Home, Users } from "../../components/icons"; +import { useCurrentProgram } from "../../hooks"; +import TabLayout, { BaseTabLayoutProps, joinPath } from "./TabLayout"; + +const { PageItem } = TabLayout; + +const NotInProgramTabLayout: React.FC = ({ + children, + basePath, +}) => { + const { currentProgram } = useCurrentProgram(); + const { public: programIsPublic } = currentProgram || { + public: false, + }; + + return ( + + + {programIsPublic ? ( + + ) : ( + <> + )} + + ); +}; + +export default NotInProgramTabLayout; diff --git a/src/layouts/TabLayout/index.tsx b/src/layouts/TabLayout/index.tsx index 136a734d..b833b808 100644 --- a/src/layouts/TabLayout/index.tsx +++ b/src/layouts/TabLayout/index.tsx @@ -1,3 +1,4 @@ export { default as AdminTabLayout } from "./AdminTabLayout"; export { default as MentorTabLayout } from "./MentorTabLayout"; export { default as MenteeTabLayout } from "./MenteeTabLayout"; +export { default as NotInProgramTabLayout } from "./NotInProgramTabLayout"; diff --git a/src/pages/program/[slug]/[profileRoute]/settings.tsx b/src/pages/program/[slug]/[profileRoute]/settings.tsx index 5f23e9f2..0ddac008 100644 --- a/src/pages/program/[slug]/[profileRoute]/settings.tsx +++ b/src/pages/program/[slug]/[profileRoute]/settings.tsx @@ -1,5 +1,11 @@ import React, { ChangeEvent, Fragment, useEffect, useState } from "react"; -import { Button, Card, Input, Text } from "../../../../components/atomic"; +import { + Button, + Card, + Input, + Text, + Toggle, +} from "../../../../components/atomic"; import CatchUnsavedChangesModal from "../../../../components/CatchUnsavedChangesModal"; import UploadIconWithPreview from "../../../../components/UploadIconWithPreview"; import { @@ -50,11 +56,19 @@ const AdminBox = (user: User) => { const SettingsPage: Page = () => { const { currentProgram, refetchCurrentProgram } = useCurrentProgram(); - const { programId, name, description, iconUrl, slug } = currentProgram || { + const { + programId, + name, + description, + iconUrl, + public: programIsPublic, + slug, + } = currentProgram || { programId: "", name: "", description: "", iconUrl: "/static/DefaultLogo.svg", + public: false, slug: "", }; @@ -228,6 +242,44 @@ const SettingsPage: Page = () => { +
+
+ + Program Privacy + +
+
+ + Turn {programIsPublic ? "on" : "off"} program privacy: + + { + updateProgram({ + variables: { + programId, + data: { + public: !programIsPublic, + }, + }, + }) + .then(() => { + refetchCurrentProgram(); + console.log("refetch"); + setSnackbarMessage({ text: "Saved settings!" }); + }) + .catch((err) => setError(err)); + }} + /> +
+
+ + Enabling program privacy means that those who are not part of your + mentorship cannot view the mentor directory. Toggling this feature + off allows anyone online to be able to view both the homepage and + the mentors under your mentorship. + +
); diff --git a/src/pages/program/[slug]/mentors.tsx b/src/pages/program/[slug]/mentors.tsx new file mode 100644 index 00000000..2165898f --- /dev/null +++ b/src/pages/program/[slug]/mentors.tsx @@ -0,0 +1,227 @@ +import { Transition } from "@headlessui/react"; +import Fuse from "fuse.js"; +import React, { Fragment, ReactNode, useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { Button, Input, Text } from "../../../components/atomic"; +import { Filter, Search } from "../../../components/icons"; +import ProfileCard from "../../../components/ProfileCard"; +import TagSelector from "../../../components/tags/TagSelector"; +import { + ProfileType, + useGetProfilesQuery, + useGetProfileTagsByProgramQuery, +} from "../../../generated/graphql"; +import { AuthorizationLevel, useCurrentProgram } from "../../../hooks"; +import AuthorizationWrapper from "../../../layouts/AuthorizationWrapper"; +import ChooseTabLayout from "../../../layouts/ChooseTabLayout"; +import PageContainer from "../../../layouts/PageContainer"; +import Page from "../../../types/Page"; + +function getAppRoot() { + if (typeof document === "undefined") return null; + return document.getElementById("__next"); +} + +interface DrawerProps { + isOpen: boolean; + title: string; + children: ReactNode; + onRequestClose: () => void; +} +const Drawer = ({ + isOpen = false, + title, + children, + onRequestClose, +}: DrawerProps) => { + const ref = useRef(null); + const handleClickOutside = (event: Event) => { + if (ref.current && !ref.current!.contains(event.target as Node)) { + onRequestClose(); + } + }; + useEffect(() => { + window.addEventListener("click", handleClickOutside, false); + return () => { + window.removeEventListener("click", handleClickOutside, false); + }; + }, []); + + const appRoot = getAppRoot(); + + if (!appRoot) return <>; + + return createPortal( + +
+
+ + {title} + + +
+ +
+
{children}
+
+
+
, + appRoot + ); +}; + +const ViewMentorsPage: Page = () => { + //const [getMentors, { mentorsData }] = useGetMentorssLazyQuery(); + //const [sortBy, setSortBy] = useState(""); + const [searchText, setSearchText] = useState(""); + const [drawerOpen, setDrawerOpen] = useState(false); + const [filteredTags, setFilteredTags] = useState([]); + + const { currentProgram } = useCurrentProgram(); + const { public: programIsPublic } = currentProgram || { + public: false, + }; + const { data } = useGetProfilesQuery({ + variables: { + programId: currentProgram?.programId!, + profileType: ProfileType.Mentor, + }, + }); + const { data: profileTagsData } = useGetProfileTagsByProgramQuery({ + variables: { programId: currentProgram?.programId! }, + }); + let unfilteredProfiles = data?.getProfiles || []; + + const fuse = new Fuse(unfilteredProfiles, { + keys: ["user.firstName", "user.lastName", "profileJson", "bio"], + }); + + const filterMentors = () => { + let newMentors = searchText + ? fuse.search(searchText).map((x) => x.item) + : unfilteredProfiles; + if (!filteredTags.length) { + return newMentors; + } + + newMentors = newMentors.filter((profile) => { + const profileTagIds = new Set( + profile.profileTags.map((tag) => tag.profileTagId) + ); + for (const tag of filteredTags) { + if (!profileTagIds.has(tag)) return false; + } + return true; + }); + + return newMentors; + }; + + const filteredMentors = filterMentors(); + + // const sortDropdown = () => { + // return ( + //
+ // Sort By + //
+ // + //
+ // ); + // }; + return ( + + {programIsPublic && ( + <> + { + setDrawerOpen(false); + }} + > + { + setFilteredTags(newSelectedTagIds); + }} + /> + +
+ + All Mentors + + {/* {sortDropdown()} */} +
+
+
+
+ +
+ { + setSearchText(e.target.value); + }} + /> +
+ +
+
+
+ +
+ +
+ {filteredMentors.map((mentor, index: number) => { + return ; + })} +
+ + )} + + ); +}; + +ViewMentorsPage.getLayout = (page, pageProps) => ( + + {page} + +); + +export default ViewMentorsPage;