diff --git a/src/app/api/organization.ts b/src/app/api/organization.ts index f175b07..7af24dc 100644 --- a/src/app/api/organization.ts +++ b/src/app/api/organization.ts @@ -124,11 +124,11 @@ export const changeOrgMembersStatus = async ( orgName: string, orgMemberStatus: { [key: string]: string } ) => { - const url = BACKEND_URL + '/api/protected/org/removeMembers/'; + const url = BACKEND_URL + '/api/protected/org/changeMembersStatus/'+orgName; const respnse = await axios.put( url, { - orgMemberStatus: orgMemberStatus, + orgMembersStatus: orgMemberStatus, }, { headers: { diff --git a/src/app/routes/BasicRoutes.tsx b/src/app/routes/BasicRoutes.tsx index cbfabf1..af764be 100644 --- a/src/app/routes/BasicRoutes.tsx +++ b/src/app/routes/BasicRoutes.tsx @@ -10,6 +10,7 @@ import Workspace from 'features/workspace'; import EditWorkspace from 'features/EditWorkspace'; import EditProject from 'features/EditProject'; +import WorkspaceMembers from 'features/workspace-members '; const BasicRoutes = () => { return ( @@ -22,6 +23,7 @@ const BasicRoutes = () => { } /> } /> }/> + }/> } /> {/* } /> */} diff --git a/src/features/WorkspaceAddMember/index.scss b/src/features/WorkspaceAddMember/index.scss new file mode 100644 index 0000000..fddf8ac --- /dev/null +++ b/src/features/WorkspaceAddMember/index.scss @@ -0,0 +1,164 @@ + + +$breakpoint-tablet: 768px; +.main_aworkspace_container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: max-content; + padding: 2rem; + .addworkspace-form-container { + background: linear-gradient(110.51deg, #141432 0.9%, #2a2a4b 101.51%); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + padding: 2rem 1rem; + border-radius: 1.25rem; + gap: 2rem; + .submit { + display: flex; + padding: 1rem 2rem; + align-self: flex-end; + font-size: 1.5rem; + border-radius: 0.875rem; + background: #402aa4; + color: white; + outline: none; + border: none; + } + } + + .single-form-element-container { + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; + .label { + color: #8181ff; + font-size: 1.5rem; + font-weight: 900; + } + } +} + +.custom-input { + box-sizing: border-box; + border: 0.8px solid #402aa4; + border-radius: 14px; + background-color: transparent; + align-items: flex-start; + color: rgba(173, 173, 255, 0.75); + padding: 1.5rem 1rem; + font-size: 1.25rem; + font-family: 'Poppins'; + outline: none; +} + +.custom-input::placeholder { + color: rgba(173, 173, 255, 0.75); + font-family: Poppins; + font-size: 1.25rem; + font-weight: 100; + line-height: normal; +} + +.file-input-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + max-width: 30rem; + padding: 2rem; + border-radius: 1.25rem; + background: linear-gradient( + 138deg, + rgba(20, 20, 50, 0.7) 5.19%, + rgba(20, 20, 50, 0.7) 97.11% + ); + backdrop-filter: blur(25px); + + .custom-file-input { + visibility: hidden; + } + .file-label { + width: fit-content; + padding: 0.4375rem 0.5625rem; + border-radius: 1.75rem; + background: #402aa4; + color: white; + } +} + +.add-member-container { + position: relative; + box-sizing: border-box; + border: 0.8px solid #402aa4; + border-radius: 14px; + display: flex; + flex-direction: row; + outline: #141432; + .custom-input { + display: flex; + width: 100%; + border: none; + outline: none; + } + button { + border: none; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.25rem; + margin: 0.75rem 1rem; + color: white; + width: 7rem; + font-weight: 100 !important; + border-radius: 2.3125rem; + background: #402aa4; + } +} + +.added-members { + display: flex; + flex-direction: row; + gap: 2rem; + flex-wrap: wrap; + width: 100%; + .member-card { + display: inline-flex; + padding: 0.4375rem 0.875rem 0.625rem 0.875rem; + justify-content: center; + align-items: center; + gap: 0.5625rem; + color: #8989ce; + border-radius: 0.75rem; + background: #26264e; + .member-avatar { + width: 40px; + height: 40px; + } + .btn-cross { + appearance: none; + border: none; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + } + } +} + +@media (min-width: $breakpoint-tablet) { + .main_aworkspace_container { + padding: 2rem 5rem; + .addworkspace-form-container { + padding: 2rem 5rem; + } + } +} diff --git a/src/features/WorkspaceAddMember/index.tsx b/src/features/WorkspaceAddMember/index.tsx new file mode 100644 index 0000000..de46672 --- /dev/null +++ b/src/features/WorkspaceAddMember/index.tsx @@ -0,0 +1,282 @@ +import { getAllUser, getUser } from 'app/api/user'; +import React, { ChangeEvent, useContext, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useQuery } from 'react-query'; +import toast from 'react-hot-toast'; +import { addOrg, addOrgMembers, getAllOrgs } from 'app/api/organization'; +import { uploadIcon } from 'app/api/file'; + +import './index.scss'; +import UserContext from 'app/context/user/userContext'; + +const WorkspaceAddMember = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + + const [selectedFile, setSelectedFile] = useState(null); + const [name, SetName] = useState(null); + const [description, setDiscription] = useState(null); + const [validDescription, setValidDescription] = useState(true); + const [validName, setValidName] = useState(false); + const [uniqueName, setUniqueName] = useState(false); + const [members, setMembers] = useState([]); + const [memberName, setMemberName] = useState(null); + + const [users, setUsers] = useState([]); + const [orgs, setOrgs] = useState([]); + + const dataFetch = async () => { + try { + if (token) { + const users_aray: string[] = []; + const org_aray: string[] = []; + const allUser = await getAllUser(token); + const allOrgs = await getAllOrgs(token); + allUser.data.users.forEach((user) => { + users_aray.push(user.username); + }); + + allOrgs.data.organizations.forEach((org) => { + org_aray.push(org.name); + }); + + setUsers(users_aray); + setOrgs(org_aray); + } + } catch (e) {} + }; + + useEffect(() => { + dataFetch(); + }, []); + + const allowedFieTypes = ['image/jpeg', 'image/jpg', 'image/png']; + + const handleFileChange = (event: ChangeEvent) => { + const file = event.target.files?.[0]; + + if (file) { + if (allowedFieTypes.includes(file.type)) { + setSelectedFile(file); + } else { + setSelectedFile(null); + toast.error('Invalid file type'); + } + } + }; + + function valid_name(str: string): boolean { + // Define a regular expression for special characters (excluding letters, digits, and spaces) + const specialCharacters = /^[a-zA-Z0-9_-]+$/; + + // Check if the string contains any special characters + return specialCharacters.test(str) && !str.endsWith('-userspace'); + } + + function isUniqueName(str: string): boolean { + return !orgs.includes(str); + } + + const handleNameChange = (event: ChangeEvent) => { + SetName(event.target.value); + setUniqueName(() => isUniqueName(event.target.value)); + setValidName(() => valid_name(event.target.value)); + }; + + const handleDesriptionChange = (event: ChangeEvent) => { + setDiscription(event.target.value); + if (description?.length) { + setValidDescription(description.length < 200); + } + }; + + const addMembers = () => { + if (memberName) { + if ( + users.includes(memberName) && + memberName != userContext?.username && + !members.includes(memberName) + ) { + setMembers([...members, memberName]); + setMemberName(null); + } + } + }; + + const removeMembers = (member: string) => { + const indexToRemove = members.indexOf(member); + + if (indexToRemove !== -1) { + const updatedMembers = [ + ...members.slice(0, indexToRemove), + ...members.slice(indexToRemove + 1), + ]; + + setMembers(updatedMembers); + } else { + console.warn(`Member "${member}" not found in the members array.`); + } + }; + const SubmitHandler = async (): Promise => { + if ( + description && + token && + name && + validName && + uniqueName && + validDescription + ) { + const func = async (): Promise => { + const dataRes = await addOrg(token, { + name: name, + description: description, + }); + + try { + if (selectedFile != null) { + const fileRes = uploadIcon(token, name, selectedFile); + } + } catch (e) {} + if (members.length > 0) { + try { + const addMmebersRes = await addOrgMembers(token, name, members); + } catch (e) {} + } + navigate('/'); + }; + + toast.promise(func(), { + loading: 'Saving Workspace', + success: Workspace saved, + error: Could not save, + }); + } else { + toast.error('Invalid inputs'); + } + }; + + return ( +
+
+
+ +
+ + +

Supported formats: JPEG, JPG, PNG

+

Selected File: {selectedFile?.name}

+
+
+
+ + + {!name ?

Name feild should not be empty

: <>} + {!validName && name ?

Not a valid name

: <>} + {!uniqueName && name ? ( +

Name already taken. Try another name

+ ) : ( + <> + )} +
+
+ + + {!description ?

Description feild should not be empty

: <>} + {!validDescription ? ( +

Characters length should be less than 200

+ ) : ( + <> + )} +
+ ) => { + setMemberName(e.target.value); + }} + placeholder='Github ID of user' + /> + +
+
+
+ {members.map((member, index) => { + return ( +
+ {' '} +

{member}

{' '} + +
+ ); + })} +
+ + +
+
+ ); +}; + +export default WorkspaceAddMember; diff --git a/src/features/workspace-members /components/BackNavigation.tsx b/src/features/workspace-members /components/BackNavigation.tsx index a3c3a33..35d43c3 100644 --- a/src/features/workspace-members /components/BackNavigation.tsx +++ b/src/features/workspace-members /components/BackNavigation.tsx @@ -1,9 +1,12 @@ +import { useNavigate } from "react-router-dom"; + const BackNavigation = () => { - const clickBack = () => { - console.log('Back button clicked'); - }; + // const clickBack = () => { + // console.log('Back button clicked'); + // }; + const navigate= useNavigate() return ( -
+
navigate("/")}> + {orgMembers && + userContext?.username && + userContext.username != name && + orgMembers[userContext?.username.toString()] == 'admin' && ( + + )}
); diff --git a/src/features/workspace-members /components/Options.tsx b/src/features/workspace-members /components/Options.tsx index d150e54..f32459b 100644 --- a/src/features/workspace-members /components/Options.tsx +++ b/src/features/workspace-members /components/Options.tsx @@ -1,8 +1,15 @@ -const Options = () => { +import { useNavigate } from "react-router-dom"; + +interface Props{ + spaceName: string +} + +const Options:React.FC = ({spaceName}) => { + const navigate= useNavigate() return (
- - + + {/* */}
); }; diff --git a/src/features/workspace-members /index.scss b/src/features/workspace-members /index.scss index 517d5ed..60986a9 100644 --- a/src/features/workspace-members /index.scss +++ b/src/features/workspace-members /index.scss @@ -122,9 +122,9 @@ font-size: 1.2rem; } .select-overlay { - padding-right: 1rem; - background: #402aa4; - cursor: pointer; + padding-right: 1rem; + background: #402aa4; + cursor: pointer; } select { border: none; @@ -136,8 +136,8 @@ cursor: pointer; } option { - padding: 1rem; - margin: 2rem; + padding: 1rem; + margin: 2rem; } } } @@ -160,6 +160,21 @@ } } +.role { + border: none; + outline: none; + color: white; + font-size: 1.2rem; + padding: 0.6rem 1rem; + background: #402aa4; + cursor: pointer; + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + +} + @media only screen and (max-width: 600px) { .member-view { width: 90%; diff --git a/src/features/workspace-members /index.tsx b/src/features/workspace-members /index.tsx index b917ee3..25c6b37 100644 --- a/src/features/workspace-members /index.tsx +++ b/src/features/workspace-members /index.tsx @@ -1,22 +1,53 @@ +import { useParams } from 'react-router-dom'; import BackNavigation from './components/BackNavigation'; import MemberCard from './components/MemberCard'; import Options from './components/Options'; import './index.scss'; +import { useEffect, useState } from 'react'; +import { getOrgMembers } from 'app/api/organization'; +import { AVATAR_URL } from 'app/constants/api'; +import { AVATAR_API } from 'envConstants'; const WorkspaceMembers = () => { - // const member = await fetch('https://api.github.com/orgs/github/public_members'); + + const {spaceName} = useParams() + const token= localStorage.getItem('token') + const [orgMembers, setOrgMembers]= useState<{[username:string]:string} | null>(null) + + const dataFetch= async()=>{ + try{ + if(spaceName&&token){ + const memRes= await getOrgMembers(token, spaceName) + + setOrgMembers(memRes.data.members) + + } + }catch(e){ + + } + } + useEffect(()=>{ + dataFetch() + },[ setOrgMembers ]) + return ( -
+ spaceName&&
- +
- { + return + })}
diff --git a/src/features/workspace-view/workspace-card/index.scss b/src/features/workspace-view/workspace-card/index.scss index 2c14e66..25b0b6b 100644 --- a/src/features/workspace-view/workspace-card/index.scss +++ b/src/features/workspace-view/workspace-card/index.scss @@ -97,3 +97,7 @@ opacity: 1; } } + +.image-stack { + margin-left: 1rem; +} \ No newline at end of file diff --git a/src/features/workspace-view/workspace-card/index.tsx b/src/features/workspace-view/workspace-card/index.tsx index b1ee358..25b3bc0 100644 --- a/src/features/workspace-view/workspace-card/index.tsx +++ b/src/features/workspace-view/workspace-card/index.tsx @@ -13,7 +13,8 @@ import { setOrgBookmarkStatus, } from 'app/api/user'; import { useNavigate } from 'react-router-dom'; - +import { AVATAR_API } from 'envConstants'; +import { AVATAR_URL } from 'app/constants/api'; type workspaceCardProps = { workspaceName: string; role: string; @@ -33,6 +34,7 @@ const WorkspaceCard = (props: workspaceCardProps) => { const [fileName, setFileName] = useState(null); const [imageSrc, setImageSrc] = useState(null); const [members, setMembers] = useState(null); + const [membersArray, setMembersArray]= useState<{username:string}[]>([]); const userContext = useContext(UserContext); const navigate = useNavigate(); @@ -51,6 +53,10 @@ const WorkspaceCard = (props: workspaceCardProps) => { try { const members_data = await getOrgMembers(token, workspaceName); setMembers(members_data.data.members); + const membersArray = Object.entries(members_data.data.members).map(([username]) => ({ username })); + setMembersArray(membersArray) + + } catch (e) {} } }; @@ -166,12 +172,23 @@ const WorkspaceCard = (props: workspaceCardProps) => {
{archeive ? 'Unarchive' : 'archive'}
- {members&&userContext?.username&&(members[userContext?.username.toString()]==="admin")&&
- delete -
} - {members&&userContext?.username&&(members[userContext?.username.toString()]==="admin")&&
navigate(`/editWorkspace/${workspaceName}`)}> - edit -
} + {members && + userContext?.username && + members[userContext?.username.toString()] === 'admin' && ( +
+ delete +
+ )} + {members && + userContext?.username && + members[userContext?.username.toString()] === 'admin' && ( +
navigate(`/editWorkspace/${workspaceName}`)} + > + edit +
+ )}
@@ -190,8 +207,33 @@ const WorkspaceCard = (props: workspaceCardProps) => { ? "USER's WORKSPACE" : workspaceName}
-
-
img
+
navigate(`/workspaceMembers/${workspaceName}`)}> +
+
+ { membersArray.length > 0 ? ( + membersArray.slice(0, 4).map((obj) => { + const url = + AVATAR_URL + + '/' + + obj.username + + '.png?apikey=' + + AVATAR_API; + return ( + + ); + }) + ) : ( + <> +
+ + )} +
+
+
{members ? Object.keys(members).length : 0} members