From d03766d98c1bca422cf4b30aefa0f945407296e2 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna Date: Wed, 5 Apr 2023 20:56:19 +0100 Subject: [PATCH 01/24] intial commit for this PR --- .../TeamMembersConfig/team-members-config.tsx | 15 ++++++++------- components/organisms/InsightPage/InsightPage.tsx | 10 +++++++++- lib/hooks/useFollowUser.ts | 1 - lib/hooks/useInsightMembers.ts | 3 +++ next-types.d.ts | 7 +++++++ 5 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 lib/hooks/useInsightMembers.ts diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 6804b4ad7b..5fcedbbd18 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -14,16 +14,17 @@ export interface TeamMemberData { } const TeamMembersConfig = ({ className, members }: TeamMembersConfigProps) => { - return ( -
-

Add Team Members

-
- - +
+

Add Team Members

+
+ +
- {members.map(member => ( + {members.map((member) => ( ))}
diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index fb095e8e60..637b604499 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -21,6 +21,7 @@ import { useDebounce } from "rooks"; import SuggestedRepositoriesList from "../SuggestedRepoList/suggested-repo-list"; import { RepoCardProfileProps } from "components/molecules/RepoCardProfile/repo-card-profile"; import { useToast } from "lib/hooks/useToast"; +import TeamMembersConfig from "components/molecules/TeamMembersConfig/team-members-config"; enum RepoLookupError { Initial = 0, @@ -43,6 +44,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { if (router.query.selectedRepos) { receivedData = JSON.parse(router.query.selectedRepos as string); } + console.log(insight); // Loading States const [deleteLoading, setDeleteLoading] = useState(false); @@ -350,7 +352,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {/* insights.opensauced.pizza/pages/{username}/{`{pageId}`}/dashboard */}
-
+
Add Repositories @@ -400,6 +402,12 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => {
+ {edit && ( +
+ +
+ )} + {edit && (
diff --git a/lib/hooks/useFollowUser.ts b/lib/hooks/useFollowUser.ts index 4d2afc5eb2..e524060dd0 100644 --- a/lib/hooks/useFollowUser.ts +++ b/lib/hooks/useFollowUser.ts @@ -1,5 +1,4 @@ import publicApiFetcher from "lib/utils/public-api-fetcher"; -import { useEffect, useState } from "react"; import useSWR, { Fetcher } from "swr"; import useSupabaseAuth from "./useSupabaseAuth"; diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts new file mode 100644 index 0000000000..e6279988c6 --- /dev/null +++ b/lib/hooks/useInsightMembers.ts @@ -0,0 +1,3 @@ +interface PaginatedInsightMembers {} + +const useInsightMembers = () => {}; diff --git a/next-types.d.ts b/next-types.d.ts index 7025233a04..ff1811ad5e 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -98,6 +98,13 @@ interface DbInsight { readonly accepted_repo_total: number; } +interface DbInsightMember { + readonly id: string; + readonly insight_id: number; + readonly user_id: number; + readonly name: string; + readonly access: "view" | "edit" | "admin"; +} interface DbUserInsight { readonly id: number; readonly user_id: number; From 8e70e6106d6d83f5c1e2dfd557d8a6b1b62daca9 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Thu, 6 Apr 2023 21:29:27 +0100 Subject: [PATCH 02/24] chore: update db types --- components/organisms/InsightPage/InsightPage.tsx | 16 +++++++++++++--- next-types.d.ts | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 637b604499..22200c97d7 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -21,7 +21,9 @@ import { useDebounce } from "rooks"; import SuggestedRepositoriesList from "../SuggestedRepoList/suggested-repo-list"; import { RepoCardProfileProps } from "components/molecules/RepoCardProfile/repo-card-profile"; import { useToast } from "lib/hooks/useToast"; -import TeamMembersConfig from "components/molecules/TeamMembersConfig/team-members-config"; +import TeamMembersConfig, { TeamMemberData } from "components/molecules/TeamMembersConfig/team-members-config"; +import useInsightMembers from "lib/hooks/useInsightMembers"; +import { useFetchUser } from "lib/hooks/useFetchUser"; enum RepoLookupError { Initial = 0, @@ -44,7 +46,15 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { if (router.query.selectedRepos) { receivedData = JSON.parse(router.query.selectedRepos as string); } - console.log(insight); + // console.log(insight); + + const { data } = useInsightMembers(insight?.id || 0); + + const insightOwner: TeamMemberData = { + name: insight?.user.name || "", + avatarUrl: getAvatarByUsername(insight?.user.login || ""), + role: "admin" + }; // Loading States const [deleteLoading, setDeleteLoading] = useState(false); @@ -404,7 +414,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {edit && ( <div className="pt-12 mt-12 border-t border-light-slate-8"> - <TeamMembersConfig members={[]} /> + <TeamMembersConfig members={[insightOwner]} /> </div> )} diff --git a/next-types.d.ts b/next-types.d.ts index ff1811ad5e..00aace1155 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -107,7 +107,7 @@ interface DbInsightMember { } interface DbUserInsight { readonly id: number; - readonly user_id: number; + readonly user: DbUser; readonly name: string; readonly is_public: boolean; readonly is_favorite: boolean; From e381c0ff8b584e0401fbfbb742626cf51e590a96 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Thu, 6 Apr 2023 21:31:07 +0100 Subject: [PATCH 03/24] feat: create insight members hook --- .../TeamMemberRow/team-member-row.tsx | 26 +++++++++++++------ lib/hooks/useInsightMembers.ts | 24 +++++++++++++++-- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 76593ace33..535c4e3d9a 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -10,21 +10,20 @@ interface TeamMemberRowProps extends TeamMemberData { } const mapRoleToText: Record<TeamMemberRowProps["role"], string> = { - admin: "Admin", + admin: "Owner", editor: "can edit", viewer: "can view", pending: "Pending" }; -const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) => { - +const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const pending = role == "pending"; const handleRoleChange = (role: string) => {}; - return( + return ( <div className={`flex justify-between items-center ${className && className} ${pending && "text-light-slate-10"}`}> <div className="flex items-center"> <Avatar size={40} isCircle avatarURL={pending ? pendingImg : avatarUrl} /> @@ -32,14 +31,25 @@ const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps </div> <div> <div className="flex items-center gap-3"> - {mapRoleToText[role]} {!pending && <AiOutlineCaretDown onClick={() => {setIsMenuOpen(!isMenuOpen);}} />} + {mapRoleToText[role]}{" "} + {!pending && ( + <AiOutlineCaretDown + onClick={() => { + setIsMenuOpen(!isMenuOpen); + }} + /> + )} </div> - { !pending && isMenuOpen && ( - <Selector filterOptions={["Admin", "can edit", "can view"]} selected={mapRoleToText[role]} variation="check" handleFilterClick={handleRoleChange} /> + {!pending && isMenuOpen && ( + <Selector + filterOptions={["Owner", "can edit", "can view"]} + selected={mapRoleToText[role]} + variation="check" + handleFilterClick={handleRoleChange} + /> )} </div> </div> ); - }; export default TeamMemberRow; diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index e6279988c6..898b759f7f 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -1,3 +1,23 @@ -interface PaginatedInsightMembers {} +import useSWR, { Fetcher } from "swr"; +import useSupabaseAuth from "./useSupabaseAuth"; +import publicApiFetcher from "lib/utils/public-api-fetcher"; +interface PaginatedInsightMembers { + data: DbInsightMember[]; + meta: Meta; +} -const useInsightMembers = () => {}; +const useInsightMembers = (page_id: number) => { + const { data, error, mutate } = useSWR<PaginatedInsightMembers, Error>( + `user/insights/${page_id}/members`, + publicApiFetcher as Fetcher<PaginatedInsightMembers, Error> + ); + + return { + data: data ?? [], + isLoading: !error && !data, + isError: !!error, + mutate + }; +}; + +export default useInsightMembers; From 2c2b907a3503cdc978433bfdb045a03227f238d4 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Thu, 6 Apr 2023 21:32:13 +0100 Subject: [PATCH 04/24] chore: copy text updates and fix insights data error --- .../molecules/TeamMemberRow/team-member-row.tsx | 2 +- components/organisms/InsightPage/InsightPage.tsx | 7 +++---- pages/hub/insights/[insightId]/edit.tsx | 12 +++--------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 535c4e3d9a..c776edc85c 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -42,7 +42,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) </div> {!pending && isMenuOpen && ( <Selector - filterOptions={["Owner", "can edit", "can view"]} + filterOptions={["Admin", "can edit", "can view"]} selected={mapRoleToText[role]} variation="check" handleFilterClick={handleRoleChange} diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 22200c97d7..3e0ba415bd 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -23,7 +23,6 @@ import { RepoCardProfileProps } from "components/molecules/RepoCardProfile/repo- import { useToast } from "lib/hooks/useToast"; import TeamMembersConfig, { TeamMemberData } from "components/molecules/TeamMembersConfig/team-members-config"; import useInsightMembers from "lib/hooks/useInsightMembers"; -import { useFetchUser } from "lib/hooks/useFetchUser"; enum RepoLookupError { Initial = 0, @@ -293,10 +292,10 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { { ...(providerToken ? { - headers: { - Authorization: `Bearer ${providerToken}` + headers: { + Authorization: `Bearer ${providerToken}` + } } - } : {}) } ); diff --git a/pages/hub/insights/[insightId]/edit.tsx b/pages/hub/insights/[insightId]/edit.tsx index 99e69ade52..7fa3db4c29 100644 --- a/pages/hub/insights/[insightId]/edit.tsx +++ b/pages/hub/insights/[insightId]/edit.tsx @@ -14,7 +14,7 @@ const EditInsightPage: WithPageLayout = () => { const { insightId } = router.query; const id = insightId as string; const { data: insight, isLoading: insightLoading, isError: insightError } = useInsight(id); - const insightRepos = insight?.repos.map(repo => repo.repo_id); + const insightRepos = insight?.repos.map((repo) => repo.repo_id); const { data: repos } = useRepositories(insightRepos); if (insightLoading) { @@ -25,17 +25,11 @@ const EditInsightPage: WithPageLayout = () => { return <>Error</>; } - if (insight && Number(insight.user_id) !== Number(user?.user_metadata.sub)) { + if (insight && Number(insight.user.id) !== Number(user?.user_metadata.sub)) { return <>Unauthorized</>; } - return ( - <InsightPage - edit={true} - insight={insight} - pageRepos={repos} - /> - ); + return <InsightPage edit={true} insight={insight} pageRepos={repos} />; }; EditInsightPage.PageLayout = HubLayout; From 9ba88c64a29efe9aebd18ee72e232e7dc1237345 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 10 Apr 2023 15:34:19 +0100 Subject: [PATCH 05/24] chore: validate email --- .../TeamMemberRow/team-member-row.tsx | 2 +- .../TeamMembersConfig/team-members-config.tsx | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index c776edc85c..53e9179d38 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -31,7 +31,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) </div> <div> <div className="flex items-center gap-3"> - {mapRoleToText[role]}{" "} + {mapRoleToText[role]} {!pending && ( <AiOutlineCaretDown onClick={() => { diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 5fcedbbd18..17a44bf853 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -1,6 +1,8 @@ import Button from "components/atoms/Button/button"; import Search from "components/atoms/Search/search"; import TeamMemberRow from "../TeamMemberRow/team-member-row"; +import { useState } from "react"; +import { validateEmail } from "lib/utils/validate-email"; interface TeamMembersConfigProps { className?: string; @@ -14,12 +16,24 @@ export interface TeamMemberData { } const TeamMembersConfig = ({ className, members }: TeamMembersConfigProps) => { + const [validInput, setValidInput] = useState(false); + const handleChange = (value: string) => { + if (validateEmail(value)) { + console.log("valid email"); + setValidInput(true); + } + }; return ( <div className={` ${className && className}`}> <h2 className="text-lg font-medium tracking-wide">Add Team Members</h2> <div className="flex items-center gap-5 mt-3"> - <Search placeholder="Search Email" name="search" className="flex-1 text-base" /> - <Button variant="primary" className="flex items-center h-7"> + <Search + onChange={(value) => handleChange(value)} + placeholder="Search Email" + name="search" + className="flex-1 text-base" + /> + <Button disabled={!validInput} variant="primary" className="flex items-center h-7"> Send Invite </Button> </div> From 2d3489d888623ed503b49d141739380c00f1603b Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Fri, 14 Apr 2023 14:30:44 +0100 Subject: [PATCH 06/24] feat: setup team invite --- .../TeamMemberRow/team-member-row.tsx | 10 +++--- .../TeamMembersConfig/team-members-config.tsx | 32 +++++++++++++++---- next-types.d.ts | 13 ++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 53e9179d38..f91158eaf6 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -9,17 +9,17 @@ interface TeamMemberRowProps extends TeamMemberData { className?: string; } -const mapRoleToText: Record<TeamMemberRowProps["role"], string> = { +const mapRoleToText: Record<TeamMemberRowProps["access"], string> = { admin: "Owner", editor: "can edit", viewer: "can view", pending: "Pending" }; -const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) => { +const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); - const pending = role == "pending"; + const pending = access == "pending"; const handleRoleChange = (role: string) => {}; @@ -31,7 +31,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) </div> <div> <div className="flex items-center gap-3"> - {mapRoleToText[role]} + {mapRoleToText[access]} {!pending && ( <AiOutlineCaretDown onClick={() => { @@ -43,7 +43,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, role }: TeamMemberRowProps) {!pending && isMenuOpen && ( <Selector filterOptions={["Admin", "can edit", "can view"]} - selected={mapRoleToText[role]} + selected={mapRoleToText[access]} variation="check" handleFilterClick={handleRoleChange} /> diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 17a44bf853..898dd3e547 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -7,38 +7,56 @@ import { validateEmail } from "lib/utils/validate-email"; interface TeamMembersConfigProps { className?: string; members: TeamMemberData[]; + onAddmember: Function; } export interface TeamMemberData { + id: string; name: string; avatarUrl: string; - role: "admin" | "editor" | "viewer" | "pending"; + access: "admin" | "editor" | "viewer" | "pending"; } -const TeamMembersConfig = ({ className, members }: TeamMembersConfigProps) => { +const TeamMembersConfig = ({ className, members, onAddmember }: TeamMembersConfigProps) => { const [validInput, setValidInput] = useState(false); - const handleChange = (value: string) => { + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); + const [allMembers, setAllMembers] = useState(members); + const handleChange = async (value: string) => { + setEmail(value); if (validateEmail(value)) { - console.log("valid email"); setValidInput(true); } }; + + const handleAddMember = async () => { + setLoading(true); + const res = await onAddmember(email); + setLoading(false); + if (res) { + setAllMembers((prev) => [...prev, res]); + } + + setEmail(""); + }; return ( <div className={` ${className && className}`}> <h2 className="text-lg font-medium tracking-wide">Add Team Members</h2> <div className="flex items-center gap-5 mt-3"> <Search + isLoading={loading} + // value={email} onChange={(value) => handleChange(value)} - placeholder="Search Email" + placeholder="Enter email address" name="search" className="flex-1 text-base" /> - <Button disabled={!validInput} variant="primary" className="flex items-center h-7"> + <Button onClick={handleAddMember} disabled={!validInput} variant="primary" className="flex items-center h-7"> Send Invite </Button> </div> <div className="mt-7"> - {members.map((member) => ( + {allMembers.map((member) => ( <TeamMemberRow key={member.name + member.avatarUrl} className="mb-4" {...member} /> ))} </div> diff --git a/next-types.d.ts b/next-types.d.ts index 00aace1155..1ef00eb33f 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -191,3 +191,16 @@ interface GhPRInfoResponse { readonly user: GhUser; readonly created_at: string; } + +interface DbInsightMember { + readonly id: number; + readonly insight_id: number; + readonly user_id: number; + readonly name: string; + readonly access: "admin" | "editor" | "viewer" | "pending"; + readonly created_at: string; + readonly updated_at: string; + readonly deleted_at: string; + readonly invitation_emailed_at: string; + readonly invitation_email: string; +} From a2008bfa0d061393aa466027d19681d39b11ae22 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Fri, 14 Apr 2023 14:31:36 +0100 Subject: [PATCH 07/24] feat: add teams hook --- .../organisms/InsightPage/InsightPage.tsx | 10 ++-- lib/hooks/useInsightMembers.ts | 52 ++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 3e0ba415bd..c8b03b0a05 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -47,12 +47,16 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { } // console.log(insight); - const { data } = useInsightMembers(insight?.id || 0); + const { data, addMember } = useInsightMembers(insight?.id || 0); + + console.log(data); + console.log(insight?.id); + console.log(sessionToken); const insightOwner: TeamMemberData = { name: insight?.user.name || "", avatarUrl: getAvatarByUsername(insight?.user.login || ""), - role: "admin" + access: "admin" }; // Loading States @@ -413,7 +417,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {edit && ( <div className="pt-12 mt-12 border-t border-light-slate-8"> - <TeamMembersConfig members={[insightOwner]} /> + <TeamMembersConfig onAddmember={addMember} members={[insightOwner]} /> </div> )} diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index 898b759f7f..4407ce72bb 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -7,16 +7,66 @@ interface PaginatedInsightMembers { } const useInsightMembers = (page_id: number) => { + const { sessionToken } = useSupabaseAuth(); + const { data, error, mutate } = useSWR<PaginatedInsightMembers, Error>( `user/insights/${page_id}/members`, publicApiFetcher as Fetcher<PaginatedInsightMembers, Error> ); + const addMember = async (email: string) => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members`, { + method: "POST", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + }, + body: JSON.stringify({ email }) + }); + + if (!req.ok) { + console.log(req.status, req.statusText); + return undefined; + } else { + return req.json(); + } + }; + + const updateMember = async (memberId: string, access: "edit" | "view" | "admin") => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members/${memberId}`, { + method: "PATCH", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + }, + body: JSON.stringify({ access }) + }); + + if (!req.ok) { + console.log(req.status, req.statusText); + return undefined; + } else { + return req.json(); + } + }; + + const deleteMember = async (memberId: string) => { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members/${memberId}`, { + method: "DELETE", + headers: { + "Content-type": "application/json", + Authorization: `Bearer ${sessionToken}` + } + }); + }; + return { data: data ?? [], isLoading: !error && !data, isError: !!error, - mutate + mutate, + addMember, + updateMember }; }; From 14e12ceeb8ecc2163dfd94f214142261cef39731 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Sat, 15 Apr 2023 20:08:08 +0100 Subject: [PATCH 08/24] feat: update member role implementation --- .../TeamMemberRow/team-member-row.tsx | 14 +++++-- .../TeamMembersConfig/team-members-config.tsx | 38 ++++++++++++------- .../organisms/InsightPage/InsightPage.tsx | 19 +++++++--- next-types.d.ts | 22 ++++------- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index f91158eaf6..810e4664bd 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -7,12 +7,14 @@ import { TeamMemberData } from "../TeamMembersConfig/team-members-config"; interface TeamMemberRowProps extends TeamMemberData { className?: string; + onDelete: Function; + onUpdate: Function; } const mapRoleToText: Record<TeamMemberRowProps["access"], string> = { admin: "Owner", - editor: "can edit", - viewer: "can view", + edit: "can edit", + view: "can view", pending: "Pending" }; @@ -21,7 +23,11 @@ const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProp const pending = access == "pending"; - const handleRoleChange = (role: string) => {}; + const handleRoleChange = async (role: string) => { + console.log(role); + if (role === "pending") { + } + }; return ( <div className={`flex justify-between items-center ${className && className} ${pending && "text-light-slate-10"}`}> @@ -42,7 +48,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProp </div> {!pending && isMenuOpen && ( <Selector - filterOptions={["Admin", "can edit", "can view"]} + filterOptions={["Admin", "can edit", "can view", "remove"]} selected={mapRoleToText[access]} variation="check" handleFilterClick={handleRoleChange} diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 898dd3e547..9bc7a4fa08 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -7,21 +7,28 @@ import { validateEmail } from "lib/utils/validate-email"; interface TeamMembersConfigProps { className?: string; members: TeamMemberData[]; - onAddmember: Function; + onAddMember: Function; + onDeleteMember: Function; + onUpdateMember: Function; } -export interface TeamMemberData { - id: string; - name: string; +export type MemberAccess = "admin" | "edit" | "view" | "pending"; +export interface TeamMemberData extends DbInsightMember { avatarUrl: string; - access: "admin" | "editor" | "viewer" | "pending"; + email?: string; } -const TeamMembersConfig = ({ className, members, onAddmember }: TeamMembersConfigProps) => { +const TeamMembersConfig = ({ + className, + members, + onAddMember, + onDeleteMember, + onUpdateMember +}: TeamMembersConfigProps) => { const [validInput, setValidInput] = useState(false); const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); - const [allMembers, setAllMembers] = useState(members); + const handleChange = async (value: string) => { setEmail(value); if (validateEmail(value)) { @@ -31,14 +38,13 @@ const TeamMembersConfig = ({ className, members, onAddmember }: TeamMembersConfi const handleAddMember = async () => { setLoading(true); - const res = await onAddmember(email); + const res = await onAddMember(email); setLoading(false); - if (res) { - setAllMembers((prev) => [...prev, res]); - } setEmail(""); }; + + // console.log(members); return ( <div className={` ${className && className}`}> <h2 className="text-lg font-medium tracking-wide">Add Team Members</h2> @@ -56,8 +62,14 @@ const TeamMembersConfig = ({ className, members, onAddmember }: TeamMembersConfi </Button> </div> <div className="mt-7"> - {allMembers.map((member) => ( - <TeamMemberRow key={member.name + member.avatarUrl} className="mb-4" {...member} /> + {members.map((member) => ( + <TeamMemberRow + onUpdate={onUpdateMember} + onDelete={onDeleteMember} + key={member.id} + className="mb-4" + {...member} + /> ))} </div> </div> diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index c8b03b0a05..7cb7450e59 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -13,7 +13,7 @@ import RepoNotIndexed from "components/organisms/Repositories/repository-not-ind import DeleteInsightPageModal from "./DeleteInsightPageModal"; import useSupabaseAuth from "lib/hooks/useSupabaseAuth"; -import { getAvatarByUsername } from "lib/utils/github"; +import { getAvatarById, getAvatarByUsername } from "lib/utils/github"; import useStore from "lib/store"; import Error from "components/atoms/Error/Error"; import Search from "components/atoms/Search/search"; @@ -45,15 +45,22 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { if (router.query.selectedRepos) { receivedData = JSON.parse(router.query.selectedRepos as string); } - // console.log(insight); + console.log(sessionToken); const { data, addMember } = useInsightMembers(insight?.id || 0); - console.log(data); - console.log(insight?.id); - console.log(sessionToken); + const Members = + data && + data.map((member) => ({ + ...member, + email: member.invitation_email, + avatarUrl: !!member.user_id ? getAvatarById(String(member.user_id)) : "" + })); + console.log(Members); const insightOwner: TeamMemberData = { + email: insight?.user.email || "", + id: String(insight?.user.id), name: insight?.user.name || "", avatarUrl: getAvatarByUsername(insight?.user.login || ""), access: "admin" @@ -417,7 +424,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {edit && ( <div className="pt-12 mt-12 border-t border-light-slate-8"> - <TeamMembersConfig onAddmember={addMember} members={[insightOwner]} /> + <TeamMembersConfig onAddmember={addMember} members={[insightOwner, ...Members]} /> </div> )} diff --git a/next-types.d.ts b/next-types.d.ts index cb485eebe0..48931ddbf6 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -99,11 +99,16 @@ interface DbInsight { } interface DbInsightMember { - readonly id: string; + readonly id: number; readonly insight_id: number; readonly user_id: number; readonly name: string; - readonly access: "view" | "edit" | "admin"; + readonly access: "pending" | "admin" | "edit" | "view"; + readonly created_at: string; + readonly updated_at: string; + readonly deleted_at: string; + readonly invitation_emailed_at: string; + readonly invitation_email: string; } interface DbUserInsight { readonly id: number; @@ -192,19 +197,6 @@ interface GhPRInfoResponse { readonly created_at: string; } -interface DbInsightMember { - readonly id: number; - readonly insight_id: number; - readonly user_id: number; - readonly name: string; - readonly access: "admin" | "editor" | "viewer" | "pending"; - readonly created_at: string; - readonly updated_at: string; - readonly deleted_at: string; - readonly invitation_emailed_at: string; - readonly invitation_email: string; -} - interface DbEmojis { readonly id: string; readonly name: string; From b59bab40a33ce9d5a0418b4bacf96b2a35723470 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Sat, 15 Apr 2023 20:08:26 +0100 Subject: [PATCH 09/24] chore: minor changes --- lib/hooks/useInsightMembers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index 4407ce72bb..3de5499c0c 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -61,12 +61,13 @@ const useInsightMembers = (page_id: number) => { }; return { - data: data ?? [], + data: data?.data ?? [], isLoading: !error && !data, isError: !!error, mutate, addMember, - updateMember + updateMember, + deleteMember }; }; From 377b3c2e7be883ac3d5eb8ba1553f48976b513a1 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 14:33:32 +0100 Subject: [PATCH 10/24] refactor: improve search component --- components/atoms/Search/search.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/atoms/Search/search.tsx b/components/atoms/Search/search.tsx index 33f4203f24..3d42188c6b 100644 --- a/components/atoms/Search/search.tsx +++ b/components/atoms/Search/search.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { GrClose } from "react-icons/gr"; import { FaSearch } from "react-icons/fa"; import { Spinner } from "../SpinLoader/spin-loader"; @@ -44,6 +44,10 @@ const Search = ({ onChange?.(""); }; + useEffect(() => { + setSearch(value); + }, [value]); + const handleOnSelect = (suggestion: string) => { setSearch(suggestion); onSearch?.(suggestion); From 99960a8b8764abb48d8277860a775f0375f1a329 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 14:34:00 +0100 Subject: [PATCH 11/24] refactor: improve select filter for teams role --- components/atoms/Selector/selector.tsx | 61 ++++++++++++-------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/components/atoms/Selector/selector.tsx b/components/atoms/Selector/selector.tsx index 427d35e29f..13a36af095 100644 --- a/components/atoms/Selector/selector.tsx +++ b/components/atoms/Selector/selector.tsx @@ -2,46 +2,39 @@ import Radio from "components/atoms/Radio/radio"; import RadioCheck from "../RadioCheck/radio-check"; interface SelectorProps { - filterOptions: string[]; + filterOptions: { name: string; value: string }[]; handleFilterClick: (filter: string) => void; selected?: string; - variation?: "circle" | "check" + variation?: "circle" | "check"; } -const Selector = ({ - filterOptions, - handleFilterClick, - selected, - variation = "circle" -}: SelectorProps) => { +const Selector = ({ filterOptions, handleFilterClick, selected, variation = "circle" }: SelectorProps) => { return ( - <div className="mt-2 absolute transform md:translate-x-0 space-y-1 mt-1 shadow-superlative z-10 w-72 bg-white rounded-lg px-1.5 py-2"> + <div className="mt-2 absolute transform md:translate-x-0 space-y-1 shadow-superlative z-10 w-72 bg-white rounded-lg px-1.5 py-2"> {filterOptions.length > 0 && - filterOptions.map((option, index) => { - return ( - variation === "circle" ? ( - <Radio - key={index} - onClick={() => { - handleFilterClick(option); - }} - className="!w-full" - checked={selected === option ? true : false} - > - {option} - </Radio> - ) : ( - <RadioCheck - key={index} - onClick={() => { - handleFilterClick(option); - }} - className="!w-full" - checked={selected === option ? true : false} - > - {option} - </RadioCheck> - ) + filterOptions.map(({ name, value }, index) => { + return variation === "circle" ? ( + <Radio + key={index} + onClick={() => { + handleFilterClick(value); + }} + className="!w-full" + checked={selected === value ? true : false} + > + {name} + </Radio> + ) : ( + <RadioCheck + key={index} + onClick={() => { + handleFilterClick(value); + }} + className="!w-full" + checked={selected === value ? true : false} + > + {name} + </RadioCheck> ); })} </div> From 8b92d73678cfe3ef9ef8e45187da5c27cb9a3152 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 14:34:32 +0100 Subject: [PATCH 12/24] feat: add toast for visual feedback --- .../TeamMemberRow/team-member-row.tsx | 18 ++++++++++----- .../TeamMembersConfig/team-members-config.tsx | 22 ++++++++++++++----- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 810e4664bd..8bcdec5e65 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -18,14 +18,17 @@ const mapRoleToText: Record<TeamMemberRowProps["access"], string> = { pending: "Pending" }; -const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProps) => { +const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, onUpdate, id }: TeamMemberRowProps) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const pending = access == "pending"; const handleRoleChange = async (role: string) => { - console.log(role); - if (role === "pending") { + setIsMenuOpen(false); + if (role === "remove") { + onDelete(id); + } else { + onUpdate(id, role); } }; @@ -33,7 +36,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProp <div className={`flex justify-between items-center ${className && className} ${pending && "text-light-slate-10"}`}> <div className="flex items-center"> <Avatar size={40} isCircle avatarURL={pending ? pendingImg : avatarUrl} /> - <p className="ml-3">{name}</p> + <p className="ml-3">{name || email}</p> </div> <div> <div className="flex items-center gap-3"> @@ -48,7 +51,12 @@ const TeamMemberRow = ({ className, name, avatarUrl, access }: TeamMemberRowProp </div> {!pending && isMenuOpen && ( <Selector - filterOptions={["Admin", "can edit", "can view", "remove"]} + filterOptions={[ + { name: "Admin", value: "admin" }, + { name: "can edit", value: "edit" }, + { name: "can view", value: "view" }, + { name: "remove", value: "remove" } + ]} selected={mapRoleToText[access]} variation="check" handleFilterClick={handleRoleChange} diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 9bc7a4fa08..9ebf58ac46 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -3,6 +3,7 @@ import Search from "components/atoms/Search/search"; import TeamMemberRow from "../TeamMemberRow/team-member-row"; import { useState } from "react"; import { validateEmail } from "lib/utils/validate-email"; +import { useToast } from "lib/hooks/useToast"; interface TeamMembersConfigProps { className?: string; @@ -28,30 +29,39 @@ const TeamMembersConfig = ({ const [validInput, setValidInput] = useState(false); const [email, setEmail] = useState(""); const [loading, setLoading] = useState(false); + const { toast } = useToast(); const handleChange = async (value: string) => { setEmail(value); - if (validateEmail(value)) { - setValidInput(true); - } + + setValidInput(!!validateEmail(value)); }; const handleAddMember = async () => { + const memberExists = members.find((member) => member.email === email); + + if (memberExists) { + toast({ description: "Member already exists", variant: "danger" }); + return; + } setLoading(true); const res = await onAddMember(email); setLoading(false); - + if (res) { + toast({ description: "Member added successfully", variant: "success" }); + } else { + toast({ description: "An error occurred!", variant: "danger" }); + } setEmail(""); }; - // console.log(members); return ( <div className={` ${className && className}`}> <h2 className="text-lg font-medium tracking-wide">Add Team Members</h2> <div className="flex items-center gap-5 mt-3"> <Search isLoading={loading} - // value={email} + value={email} onChange={(value) => handleChange(value)} placeholder="Enter email address" name="search" From 35eb88a95b1f1d1af0956e7e55bec649a11fd4e8 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 14:35:56 +0100 Subject: [PATCH 13/24] chore: update teams crud --- lib/hooks/useInsightMembers.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index 3de5499c0c..2083d7a39a 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -28,6 +28,7 @@ const useInsightMembers = (page_id: number) => { console.log(req.status, req.statusText); return undefined; } else { + mutate(); return req.json(); } }; @@ -44,8 +45,10 @@ const useInsightMembers = (page_id: number) => { if (!req.ok) { console.log(req.status, req.statusText); + return undefined; } else { + mutate(); return req.json(); } }; @@ -58,6 +61,10 @@ const useInsightMembers = (page_id: number) => { Authorization: `Bearer ${sessionToken}` } }); + + if (req.ok) { + mutate(); + } }; return { From b4849d216cb0873d94215db7609d880458b35488 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 16:08:14 +0100 Subject: [PATCH 14/24] refactor: minor adjustments --- .../TeamMemberRow/team-member-row.tsx | 1 + .../TeamMembersConfig/team-members-config.tsx | 8 ++++++-- .../organisms/InsightPage/InsightPage.tsx | 20 +++++++++++-------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index 8bcdec5e65..fa79cf406e 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -43,6 +43,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on {mapRoleToText[access]} {!pending && ( <AiOutlineCaretDown + className="cursor-pointer" onClick={() => { setIsMenuOpen(!isMenuOpen); }} diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 9ebf58ac46..8c7f48452b 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -13,8 +13,12 @@ interface TeamMembersConfigProps { onUpdateMember: Function; } -export type MemberAccess = "admin" | "edit" | "view" | "pending"; -export interface TeamMemberData extends DbInsightMember { +export interface TeamMemberData { + id: number; + insight_id: number; + user_id?: number; + name: string; + access: "pending" | "admin" | "edit" | "view"; avatarUrl: string; email?: string; } diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 7cb7450e59..b41581c280 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -45,9 +45,8 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { if (router.query.selectedRepos) { receivedData = JSON.parse(router.query.selectedRepos as string); } - console.log(sessionToken); - const { data, addMember } = useInsightMembers(insight?.id || 0); + const { data, addMember, deleteMember, updateMember } = useInsightMembers(insight?.id || 0); const Members = data && @@ -56,13 +55,13 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { email: member.invitation_email, avatarUrl: !!member.user_id ? getAvatarById(String(member.user_id)) : "" })); - console.log(Members); const insightOwner: TeamMemberData = { - email: insight?.user.email || "", - id: String(insight?.user.id), - name: insight?.user.name || "", - avatarUrl: getAvatarByUsername(insight?.user.login || ""), + insight_id: Number(insight?.id), + email: String(insight?.user.email), + id: Number(insight?.user.id), + name: String(insight?.user.name), + avatarUrl: getAvatarByUsername(String(insight?.user.login)), access: "admin" }; @@ -424,7 +423,12 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {edit && ( <div className="pt-12 mt-12 border-t border-light-slate-8"> - <TeamMembersConfig onAddmember={addMember} members={[insightOwner, ...Members]} /> + <TeamMembersConfig + onUpdateMember={updateMember} + onDeleteMember={deleteMember} + onAddMember={addMember} + members={[insightOwner, ...Members]} + /> </div> )} From d5f923ee5d8c348e62446070743fce7b8c26a606 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 16:10:29 +0100 Subject: [PATCH 15/24] fix: linting errors --- components/organisms/InsightPage/InsightPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index b41581c280..09b06f79c0 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -302,10 +302,10 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { { ...(providerToken ? { - headers: { - Authorization: `Bearer ${providerToken}` - } + headers: { + Authorization: `Bearer ${providerToken}` } + } : {}) } ); From e3d97eecc0970f9c3e5d653aad2ee3c60696da4c Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 16:14:42 +0100 Subject: [PATCH 16/24] fix: select tick visibility --- components/molecules/TeamMemberRow/team-member-row.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index fa79cf406e..b2e65060fc 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -58,7 +58,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on { name: "can view", value: "view" }, { name: "remove", value: "remove" } ]} - selected={mapRoleToText[access]} + selected={access} variation="check" handleFilterClick={handleRoleChange} /> From 634c3100097152281cd1d73ad38b8a355538bcfc Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 16:42:39 +0100 Subject: [PATCH 17/24] refactor: minor adjustments --- components/molecules/TeamMemberRow/team-member-row.tsx | 4 ++-- .../molecules/TeamMembersConfig/team-members-config.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index b2e65060fc..d0dbfd802c 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -34,9 +34,9 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on return ( <div className={`flex justify-between items-center ${className && className} ${pending && "text-light-slate-10"}`}> - <div className="flex items-center"> + <div className="flex items-center gap-3"> <Avatar size={40} isCircle avatarURL={pending ? pendingImg : avatarUrl} /> - <p className="ml-3">{name || email}</p> + <p>{name || email}</p> </div> <div> <div className="flex items-center gap-3"> diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 8c7f48452b..0249f1c810 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -51,12 +51,12 @@ const TeamMembersConfig = ({ setLoading(true); const res = await onAddMember(email); setLoading(false); + setEmail(""); if (res) { toast({ description: "Member added successfully", variant: "success" }); } else { toast({ description: "An error occurred!", variant: "danger" }); } - setEmail(""); }; return ( From 7f4b33dd06d5d47c7e9e247181514e1d104a0694 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 16:56:32 +0100 Subject: [PATCH 18/24] fix: build error --- .../FilterCardSelect/filter-card-select.tsx | 66 +++++++++++++------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/components/molecules/FilterCardSelect/filter-card-select.tsx b/components/molecules/FilterCardSelect/filter-card-select.tsx index d8eb969867..3daf0d5cd7 100644 --- a/components/molecules/FilterCardSelect/filter-card-select.tsx +++ b/components/molecules/FilterCardSelect/filter-card-select.tsx @@ -8,10 +8,10 @@ import repoIcon from "../../../img/icons/repo.svg"; import Selector from "../../atoms/Selector/selector"; interface FilterCardSelectProps { - selected: string; - icon?: "topic" | "repo" | "org" | "contributor"; - options: string[]; - handleFilterClick: (filter: string) => void; + selected: string; + icon?: "topic" | "repo" | "org" | "contributor"; + options: string[]; + handleFilterClick: (filter: string) => void; } const icons = { @@ -33,8 +33,12 @@ const icons = { } }; -const FilterCardSelect: React.FC<FilterCardSelectProps> = ({ selected: filterName, icon = "topic", options, handleFilterClick }) => { - +const FilterCardSelect: React.FC<FilterCardSelectProps> = ({ + selected: filterName, + icon = "topic", + options, + handleFilterClick +}) => { const ref = useRef<HTMLDivElement>(null); const [isOpen, setIsOpen] = useState(false); const toggleFilter = () => { @@ -62,22 +66,46 @@ const FilterCardSelect: React.FC<FilterCardSelectProps> = ({ selected: filterNam <div onClick={toggleFilter} ref={ref} - className={"inline-block py-1 border border-slate-300 outline-none hover:bg-slate-50 focus:ring-2 bg-slate-100 focus:ring-slate-300 rounded-lg cursor-pointer"}> + className={ + "inline-block py-1 border border-slate-300 outline-none hover:bg-slate-50 focus:ring-2 bg-slate-100 focus:ring-slate-300 rounded-lg cursor-pointer" + } + > <button className="flex items-center gap-1 mx-2"> <Image - width={14} height={14} - alt={icon === "topic" ? icons.topic.alt : icon === "org" ? icons.org.alt : icon === "contributor" ? icons.contributor.alt : icon === "repo" ? icons.repo.alt : "Icon"} - src={icon === "topic" ? icons.topic.src : icon === "org" ? icons.org.src : icon === "contributor" ? icons.contributor.src : icon === "repo" ? icons.repo.src : icons.topic.src} /> - <Text className="!text-sm font-semibold tracking-tight !text-slate-900"> - {filterName} - </Text> + width={14} + height={14} + alt={ + icon === "topic" + ? icons.topic.alt + : icon === "org" + ? icons.org.alt + : icon === "contributor" + ? icons.contributor.alt + : icon === "repo" + ? icons.repo.alt + : "Icon" + } + src={ + icon === "topic" + ? icons.topic.src + : icon === "org" + ? icons.org.src + : icon === "contributor" + ? icons.contributor.src + : icon === "repo" + ? icons.repo.src + : icons.topic.src + } + /> + <Text className="!text-sm font-semibold tracking-tight !text-slate-900">{filterName}</Text> </button> - { isOpen && <Selector - filterOptions={options} - handleFilterClick={handleFilterClick} - selected={filterName} - /> - } + {isOpen && ( + <Selector + filterOptions={options.map((option) => ({ name: option, value: option }))} + handleFilterClick={handleFilterClick} + selected={filterName} + /> + )} </div> </> ); From 1f49082bfc1a2dbd9ac4c56e77d1853233c81ed0 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 17:04:13 +0100 Subject: [PATCH 19/24] fix: update teams story --- stories/atoms/selector.stories.tsx | 11 +++++++++-- stories/atoms/team-member-row.stories.tsx | 8 ++++---- stories/atoms/team-members-config.stories.tsx | 12 +++++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/stories/atoms/selector.stories.tsx b/stories/atoms/selector.stories.tsx index c0e80237cc..c5ca9c70c9 100644 --- a/stories/atoms/selector.stories.tsx +++ b/stories/atoms/selector.stories.tsx @@ -8,6 +8,13 @@ const storyConfig = { export default storyConfig; +const options = [ + { name: "option1", value: "option1" }, + { name: "option2", value: "option2" }, + { name: "option3", value: "option3" }, + { name: "option4", value: "option4" } +]; + //Select Template const SelectTemplate: ComponentStory<typeof Selector> = (args) => <Selector {...args} />; @@ -15,12 +22,12 @@ export const Default = SelectTemplate.bind({}); export const CheckMarks = SelectTemplate.bind({}); Default.args = { - filterOptions: ["option1", "option2", "option3"], + filterOptions: options, selected: "option1" }; CheckMarks.args = { - filterOptions: ["option1", "option2", "option3"], + filterOptions: options, selected: "option1", variation: "check" }; diff --git a/stories/atoms/team-member-row.stories.tsx b/stories/atoms/team-member-row.stories.tsx index bb19612492..c5d7e700cb 100644 --- a/stories/atoms/team-member-row.stories.tsx +++ b/stories/atoms/team-member-row.stories.tsx @@ -20,23 +20,23 @@ Default.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "admin" + access: "admin" }; Editor.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "editor" + access: "edit" }; Viewer.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "viewer" + access: "view" }; Pending.args = { className: "max-w-2xl", name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "pending" + access: "pending" }; diff --git a/stories/atoms/team-members-config.stories.tsx b/stories/atoms/team-members-config.stories.tsx index 38e11cda97..68027c1dd0 100644 --- a/stories/atoms/team-members-config.stories.tsx +++ b/stories/atoms/team-members-config.stories.tsx @@ -18,17 +18,23 @@ Default.args = { { name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "admin" + access: "admin", + id: 3, + insight_id: 3 }, { name: "John Cena", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "editor" + access: "edit", + id: 4, + insight_id: 4 }, { name: "John Wick", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", - role: "viewer" + access: "view", + id: 5, + insight_id: 5 } ] }; From c9c0e94f60ba9538d7ee608236d750fbfc7a333f Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 17:15:33 +0100 Subject: [PATCH 20/24] refactor: update toast message --- components/molecules/TeamMembersConfig/team-members-config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 0249f1c810..2b9d94ebdf 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -53,7 +53,7 @@ const TeamMembersConfig = ({ setLoading(false); setEmail(""); if (res) { - toast({ description: "Member added successfully", variant: "success" }); + toast({ description: "Member invite sent successfully", variant: "success" }); } else { toast({ description: "An error occurred!", variant: "danger" }); } From df18bcca1b7cc32df32ebae58a9175bf613ef05a Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Mon, 17 Apr 2023 23:17:27 +0100 Subject: [PATCH 21/24] chore: review changes --- .../TeamMemberRow/team-member-row.tsx | 22 +++++++++++++------ .../TeamMembersConfig/team-members-config.tsx | 4 ++-- .../organisms/InsightPage/InsightPage.tsx | 6 ++--- lib/hooks/useInsightMembers.ts | 10 ++++----- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index d0dbfd802c..dfd2da03f7 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -12,7 +12,8 @@ interface TeamMemberRowProps extends TeamMemberData { } const mapRoleToText: Record<TeamMemberRowProps["access"], string> = { - admin: "Owner", + owner: "Owner", + admin: "Admin", edit: "can edit", view: "can view", pending: "Pending" @@ -22,6 +23,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on const [isMenuOpen, setIsMenuOpen] = useState(false); const pending = access == "pending"; + const isOwner = access == "owner"; const handleRoleChange = async (role: string) => { setIsMenuOpen(false); @@ -42,12 +44,18 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on <div className="flex items-center gap-3"> {mapRoleToText[access]} {!pending && ( - <AiOutlineCaretDown - className="cursor-pointer" - onClick={() => { - setIsMenuOpen(!isMenuOpen); - }} - /> + <> + {isOwner ? ( + <AiOutlineCaretDown /> + ) : ( + <AiOutlineCaretDown + className="cursor-pointer" + onClick={() => { + setIsMenuOpen(!isMenuOpen); + }} + /> + )} + </> )} </div> {!pending && isMenuOpen && ( diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index 2b9d94ebdf..dad01fe02d 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -18,7 +18,7 @@ export interface TeamMemberData { insight_id: number; user_id?: number; name: string; - access: "pending" | "admin" | "edit" | "view"; + access: "owner" | "pending" | "admin" | "edit" | "view"; avatarUrl: string; email?: string; } @@ -42,7 +42,7 @@ const TeamMembersConfig = ({ }; const handleAddMember = async () => { - const memberExists = members.find((member) => member.email === email); + const memberExists = members.find((member) => member.email?.toLowerCase() === email.toLowerCase()); if (memberExists) { toast({ description: "Member already exists", variant: "danger" }); diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 09b06f79c0..8995911c2c 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -48,7 +48,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { const { data, addMember, deleteMember, updateMember } = useInsightMembers(insight?.id || 0); - const Members = + const members = data && data.map((member) => ({ ...member, @@ -62,7 +62,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { id: Number(insight?.user.id), name: String(insight?.user.name), avatarUrl: getAvatarByUsername(String(insight?.user.login)), - access: "admin" + access: "owner" }; // Loading States @@ -427,7 +427,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { onUpdateMember={updateMember} onDeleteMember={deleteMember} onAddMember={addMember} - members={[insightOwner, ...Members]} + members={[insightOwner, ...members]} /> </div> )} diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index 2083d7a39a..27de1b3acc 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -6,16 +6,16 @@ interface PaginatedInsightMembers { meta: Meta; } -const useInsightMembers = (page_id: number) => { +const useInsightMembers = (insightId: number) => { const { sessionToken } = useSupabaseAuth(); const { data, error, mutate } = useSWR<PaginatedInsightMembers, Error>( - `user/insights/${page_id}/members`, + `user/insights/${insightId}/members`, publicApiFetcher as Fetcher<PaginatedInsightMembers, Error> ); const addMember = async (email: string) => { - const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members`, { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members`, { method: "POST", headers: { "Content-type": "application/json", @@ -34,7 +34,7 @@ const useInsightMembers = (page_id: number) => { }; const updateMember = async (memberId: string, access: "edit" | "view" | "admin") => { - const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members/${memberId}`, { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${memberId}`, { method: "PATCH", headers: { "Content-type": "application/json", @@ -54,7 +54,7 @@ const useInsightMembers = (page_id: number) => { }; const deleteMember = async (memberId: string) => { - const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${page_id}/members/${memberId}`, { + const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${memberId}`, { method: "DELETE", headers: { "Content-type": "application/json", From 571e273b751bafd2de3e6e5791b81cbb4f670cd0 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Tue, 18 Apr 2023 18:38:10 +0100 Subject: [PATCH 22/24] chore: review changes --- .../molecules/TeamMemberRow/team-member-row.tsx | 8 ++++---- .../TeamMembersConfig/team-members-config.tsx | 11 ++++++----- components/organisms/InsightPage/InsightPage.tsx | 6 +++--- lib/hooks/useInsightMembers.ts | 3 ++- next-types.d.ts | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/components/molecules/TeamMemberRow/team-member-row.tsx b/components/molecules/TeamMemberRow/team-member-row.tsx index dfd2da03f7..fdb0c84789 100644 --- a/components/molecules/TeamMemberRow/team-member-row.tsx +++ b/components/molecules/TeamMemberRow/team-member-row.tsx @@ -3,12 +3,12 @@ import { AiOutlineCaretDown } from "react-icons/ai"; import pendingImg from "img/icons/fallback-image-disabled-square.svg"; import { useState } from "react"; import Selector from "components/atoms/Selector/selector"; -import { TeamMemberData } from "../TeamMembersConfig/team-members-config"; +import { MemberAccess, TeamMemberData } from "../TeamMembersConfig/team-members-config"; interface TeamMemberRowProps extends TeamMemberData { className?: string; - onDelete: Function; - onUpdate: Function; + onDelete: (memberId: string) => void; + onUpdate: (memberId: string, access: MemberAccess) => Promise<any> | undefined; } const mapRoleToText: Record<TeamMemberRowProps["access"], string> = { @@ -30,7 +30,7 @@ const TeamMemberRow = ({ className, name, avatarUrl, access, email, onDelete, on if (role === "remove") { onDelete(id); } else { - onUpdate(id, role); + onUpdate(id, role as MemberAccess); } }; diff --git a/components/molecules/TeamMembersConfig/team-members-config.tsx b/components/molecules/TeamMembersConfig/team-members-config.tsx index dad01fe02d..a54c358e2c 100644 --- a/components/molecules/TeamMembersConfig/team-members-config.tsx +++ b/components/molecules/TeamMembersConfig/team-members-config.tsx @@ -8,17 +8,18 @@ import { useToast } from "lib/hooks/useToast"; interface TeamMembersConfigProps { className?: string; members: TeamMemberData[]; - onAddMember: Function; - onDeleteMember: Function; - onUpdateMember: Function; + onAddMember: (email: string) => Promise<any> | undefined; + onDeleteMember: (memberId: string) => void; + onUpdateMember: (memberId: string, access: MemberAccess) => Promise<any>; } +export type MemberAccess = "owner" | "pending" | "admin" | "edit" | "view"; export interface TeamMemberData { - id: number; + id: string; insight_id: number; user_id?: number; name: string; - access: "owner" | "pending" | "admin" | "edit" | "view"; + access: MemberAccess; avatarUrl: string; email?: string; } diff --git a/components/organisms/InsightPage/InsightPage.tsx b/components/organisms/InsightPage/InsightPage.tsx index 8995911c2c..66a117481d 100644 --- a/components/organisms/InsightPage/InsightPage.tsx +++ b/components/organisms/InsightPage/InsightPage.tsx @@ -59,8 +59,8 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { const insightOwner: TeamMemberData = { insight_id: Number(insight?.id), email: String(insight?.user.email), - id: Number(insight?.user.id), - name: String(insight?.user.name), + id: String(insight?.user.id), + name: String(insight?.user.name || insight?.user.login), avatarUrl: getAvatarByUsername(String(insight?.user.login)), access: "owner" }; @@ -424,7 +424,7 @@ const InsightPage = ({ edit, insight, pageRepos }: InsightPageProps) => { {edit && ( <div className="pt-12 mt-12 border-t border-light-slate-8"> <TeamMembersConfig - onUpdateMember={updateMember} + onUpdateMember={(id, access) => updateMember(id, access)} onDeleteMember={deleteMember} onAddMember={addMember} members={[insightOwner, ...members]} diff --git a/lib/hooks/useInsightMembers.ts b/lib/hooks/useInsightMembers.ts index 27de1b3acc..66126acab3 100644 --- a/lib/hooks/useInsightMembers.ts +++ b/lib/hooks/useInsightMembers.ts @@ -1,6 +1,7 @@ import useSWR, { Fetcher } from "swr"; import useSupabaseAuth from "./useSupabaseAuth"; import publicApiFetcher from "lib/utils/public-api-fetcher"; +import { MemberAccess } from "components/molecules/TeamMembersConfig/team-members-config"; interface PaginatedInsightMembers { data: DbInsightMember[]; meta: Meta; @@ -33,7 +34,7 @@ const useInsightMembers = (insightId: number) => { } }; - const updateMember = async (memberId: string, access: "edit" | "view" | "admin") => { + const updateMember = async (memberId: string, access: MemberAccess) => { const req = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/user/insights/${insightId}/members/${memberId}`, { method: "PATCH", headers: { diff --git a/next-types.d.ts b/next-types.d.ts index 6c89b6a2e8..2bdbd20c63 100644 --- a/next-types.d.ts +++ b/next-types.d.ts @@ -99,7 +99,7 @@ interface DbInsight { } interface DbInsightMember { - readonly id: number; + readonly id: string; readonly insight_id: number; readonly user_id: number; readonly name: string; From 822683235cc0c3778f3cd020dca1ef6a2f75fb3c Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Tue, 18 Apr 2023 18:45:43 +0100 Subject: [PATCH 23/24] fix: update story props --- stories/atoms/team-members-config.stories.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stories/atoms/team-members-config.stories.tsx b/stories/atoms/team-members-config.stories.tsx index 68027c1dd0..658627ac22 100644 --- a/stories/atoms/team-members-config.stories.tsx +++ b/stories/atoms/team-members-config.stories.tsx @@ -19,21 +19,21 @@ Default.args = { name: "John Doe", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", access: "admin", - id: 3, + id: "3", insight_id: 3 }, { name: "John Cena", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", access: "edit", - id: 4, + id: "4", insight_id: 4 }, { name: "John Wick", avatarUrl: "https://avatars.githubusercontent.com/u/7252105?v=4", access: "view", - id: 5, + id: "5", insight_id: 5 } ] From 9afe06377b828d4722aa6c7b3ee85ab098064453 Mon Sep 17 00:00:00 2001 From: Sunday Ogbonna <oliviamegan11@gmail.com> Date: Tue, 18 Apr 2023 19:01:06 +0100 Subject: [PATCH 24/24] chore: review changes --- .../FilterCardSelect/filter-card-select.tsx | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/components/molecules/FilterCardSelect/filter-card-select.tsx b/components/molecules/FilterCardSelect/filter-card-select.tsx index 3daf0d5cd7..c928d9afee 100644 --- a/components/molecules/FilterCardSelect/filter-card-select.tsx +++ b/components/molecules/FilterCardSelect/filter-card-select.tsx @@ -74,28 +74,8 @@ const FilterCardSelect: React.FC<FilterCardSelectProps> = ({ <Image width={14} height={14} - alt={ - icon === "topic" - ? icons.topic.alt - : icon === "org" - ? icons.org.alt - : icon === "contributor" - ? icons.contributor.alt - : icon === "repo" - ? icons.repo.alt - : "Icon" - } - src={ - icon === "topic" - ? icons.topic.src - : icon === "org" - ? icons.org.src - : icon === "contributor" - ? icons.contributor.src - : icon === "repo" - ? icons.repo.src - : icons.topic.src - } + alt={icons[icon] ? icons[icon].alt : "Icons"} + src={icons[icon] ? icons[icon].src : icons.topic.src} /> <Text className="!text-sm font-semibold tracking-tight !text-slate-900">{filterName}</Text> </button>