diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1f0d754 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +build +.github +node_modules +.eslintignore +.eslintrc.json +.prettierignore +.prettierrc diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..909276b --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +CLIENT_ID = +BACKEND_URL = +AVATAR_API = \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..5666e66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,96 @@ + +name: Bug Report +title: "[Bug] Bug title " +description: Create a report to help us identify any unintended flaws, errors, or faults. +body: + - type: checkboxes + attributes: + label: Before Creating the Bug Report + options: + - label: > + I found a bug, not just asking a question, which should be created in [GitHub Discussions](https://github.com/mdgspace/activity-leaderboard-backend/discussions). + required: true + - label: > + I have searched the [GitHub Issues](https://github.com/mdgspace/activity-leaderboard-backend/issues) and [GitHub Discussions](https://github.com/mdgspace/activity-leaderboard-backend/discussions) of this repository and believe that this is not a duplicate. + required: true + - label: > + I have confirmed that this bug belongs to the current repository, not other repositories of RocketMQ. + required: true + + - type: textarea + attributes: + label: Runtime platform environment + description: Describe the runtime platform environment. + placeholder: > + OS: (e.g., "Ubuntu 20.04") + OS: (e.g., "Windows Server 2019") + validations: + required: true + + - type: textarea + attributes: + label: Backend version + description: Describe the Backend version. + placeholder: > + branch: (e.g main) + version: (e.g. 1.0.0) + Git commit id: (e.g. c88b5cfa72e204962929eea105687647146112c6) + validations: + required: true + + - type: textarea + attributes: + label: JDK Version + description: Run or Compiler version. + placeholder: > + Compiler: (e.g., "Oracle JDK 11.0.17") + OS: (e.g., "Ubuntu 20.04") + Runtime (if different from JDK above): (e.g., "Oracle JRE 8u251") + OS (if different from OS compiled on): (e.g., "Windows Server 2019") + validations: + required: false + + - type: textarea + attributes: + label: Describe the Bug + description: Describe what happened. + placeholder: > + A clear and concise description of what the bug is. + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: Describe the steps to reproduce the bug here. + placeholder: > + If possible, provide a recipe for reproducing the error. + validations: + required: true + + - type: textarea + attributes: + label: What Did You Expect to See? + description: You expect to see result. + placeholder: > + A clear and concise description of what you expected to see. + validations: + required: true + + - type: textarea + attributes: + label: What Did You See Instead? + description: You instead to see result. + placeholder: > + A clear and concise description of what you saw instead. + validations: + required: true + + - type: textarea + attributes: + label: Additional Context + description: Additional context. + placeholder: > + Add any other context about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8a095cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ + + +blank_issues_enabled: false +contact_links: + - name: Ask Question + url: https://github.com/mdgspace/activity-leaderboard-backend/discussions + about: Please go to GitHub Disccusions to ask questions diff --git a/.github/ISSUE_TEMPLATE/doc.yml b/.github/ISSUE_TEMPLATE/doc.yml new file mode 100644 index 0000000..540229f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc.yml @@ -0,0 +1,39 @@ + +name: Documentation Related +title: "[Doc] Documentation Related " +description: I find some issues related to the documentation. +labels: [ "module/doc" ] +body: + - type: checkboxes + attributes: + label: Search before creation + description: > + Please make sure to search in the [issues](https://github.com/mdgspace/activity-leaderboard-backend/issues) + first to see whether the same issue was reported already. + options: + - label: > + I had searched in the [issues](https://github.com/mdgspace/activity-leaderboard-backend/issues) and found + no similar issues. + required: true + + - type: textarea + attributes: + label: Documentation Related + description: Describe the suggestion about document. + placeholder: > + e.g There is a typo + validations: + required: true + + - type: checkboxes + attributes: + label: Are you willing to submit PR? + description: > + This is absolutely not required, but we are happy to guide you in the contribution process + especially if you already have a good understanding of how to implement the fix. + options: + - label: Yes I am willing to submit a PR! + + - type: markdown + attributes: + value: "Thanks for completing our form!" diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.yml b/.github/ISSUE_TEMPLATE/enhancement_request.yml new file mode 100644 index 0000000..ab04414 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement_request.yml @@ -0,0 +1,59 @@ + + +name: Enhancement Request +title: "[Enhancement] Enhancement title" +description: Suggest an enhancement for this project +labels: [ "type/enhancement" ] +body: + - type: checkboxes + attributes: + label: Before Creating the Enhancement Request + description: > + Most of issues should be classified as bug or feature request. An issue should be considered as an enhancement when it proposes improvements to + existing functionality or user experience, without necessarily introducing new features or fixing existing bugs. + options: + - label: > + I have confirmed that this should be classified as an enhancement rather than a bug/feature. + required: true + + - type: textarea + attributes: + label: Summary + placeholder: > + A clear and concise description of the enhancement you would like to see in the project. + validations: + required: true + + - type: textarea + attributes: + label: Motivation + placeholder: > + Explain why you believe this enhancement is necessary, and how it benefits the project and community. + Include any specific use cases that you have in mind. + validations: + required: true + + - type: textarea + attributes: + label: Describe the Solution You'd Like + placeholder: > + Describe the enhancement you propose, detailing the change and implementation steps involved. + If you have multiple solutions, please list them separately. + validations: + required: true + + - type: textarea + attributes: + label: Describe Alternatives You've Considered + placeholder: > + List any alternative enhancements or implementations you have considered, and explain why they may not be as effective or appropriate. + validations: + required: true + + - type: textarea + attributes: + label: Additional Context + placeholder: > + Add any relevant context, screenshots, prototypes, or other supplementary information to help illustrate the enhancement. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..435718e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,41 @@ + +name: Feature Request +title: "[Feature] New feature title" +description: Suggest an idea for this project. +labels: [ "type/new feature" ] +body: + - type: textarea + attributes: + label: Is Your Feature Request Related to a Problem? + description: Please Describe It. + placeholder: > + A clear and concise description of what the problem is. + validations: + required: true + + - type: textarea + attributes: + label: Describe the Solution You'd Like + description: Describe how you solved it. + placeholder: > + A clear and concise description of what you want to happen. + validations: + required: true + + - type: textarea + attributes: + label: Describe Alternatives You've Considered + description: Describe your solution + placeholder: > + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + + - type: textarea + attributes: + label: Additional Context + description: Additional context. + placeholder: > + Add any other context about the problem here. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..96bffa5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ + + +### Which Issue(s) This PR Fixes + + + +Fixes #issue_id + +### Brief Description + + + +### How Did You Test This Change? + + diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..5e08480 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,23 @@ +# Deployment Guide + +## Prerequisites +- [Git](https://git-scm.com/downloads) +- [Docker](https://docs.docker.com/engine/install/) +- [MultiAvatar api secret Key](https://api.multiavatar.com/) +- [Github OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) + +## Steps + +### Clone the repo +``` + git clone https://github.com/mdgspace/activity-leaderboard.git + + cd activity-leaderboard +``` + +### Create .env using .env.example + +### Run docker-compose +``` +docker compose up -d +``` diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..7fd9222 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,31 @@ +# Deployment Guide + +## Prerequisites +- [Git](https://git-scm.com/downloads) +- [Docker](https://docs.docker.com/engine/install/) +- [nodejs](https://nodejs.org/en/download) +- [MultiAvatar api secret Key](https://api.multiavatar.com/) +- [Github OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) + +## Steps + +### Clone the repo +``` + git clone https://github.com/mdgspace/activity-leaderboard.git + + cd activity-leaderboard +``` + +### Create .env using .env.example + +### Run +1. `npm ci` +2. `npm run prepare` +3. `npm run lint` +4. `npm run prettier` + +### Run appplication + +``` +npm start +``` \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b871830 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM node:latest as build + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm install + +COPY . ./ + +RUN npm run build + + +FROM nginx:alpine + +COPY --from=build /app/build /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..bd35dd0 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,24 @@ +# Start from a Node base image +FROM node:latest +# Set the working directory +WORKDIR /app +# Set environment variables +ARG CLIENT_ID +ENV CLIENT_ID=${CLIENT_ID} +ARG BACKEND_URL +ENV BACKEND_URL=${BACKEND_URL} +ARG AVATAR_API +ENV AVATAR_API=${AVATAR_API} +# Copy package.json and package-lock.json +COPY package*.json ./ +# Install dependencies +RUN npm install +# Copy the rest of the code +COPY . . +# Build the app +RUN npm run build +# Expose the port the app runs on +EXPOSE 3000 +# Start the application +CMD ["npm", "start"] + diff --git a/README.md b/README.md index 9d368cc..788aa04 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,21 @@ -# Activity Leaderboard +# Activity-leaderboard Frontend -## Setup +### Activity Leader Board is a open source project which can be used to monitor progress in an Organization . -1. `npm ci` -2. `npm run prepare` -3. `npm run lint` -4. `npm run prettier` + + +## Tech Stack + +**Backend:** SpringBoot + +**Frontend:** React + +**Cloud:** AWS + +**Database**: Postgres + +**Caching**: Redis + + +### Activity-leaderboard Backend +[Backend](https://github.com/mdgspace/activity-leaderboard.git) \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 6df6d6e..c6b91e0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,11 +1,15 @@ -version: '3.3' +version: '3.8' + services: - api: - build: - context: ./ - dockerfile: local.Dockerfile - volumes: - - ./:/app - - ./.m2:/root/.m2 - working_dir: /app - command: sh run.sh \ No newline at end of file + frontend: + build: + context: . + dockerfile: Dockerfile + env_file: + - .env + ports: + - "8081:3000" + volumes: + - .:/app + stdin_open: true + tty: true diff --git a/package-lock.json b/package-lock.json index 28f414a..2c3c5ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "activity-leaderboard", "version": "0.1.0", "dependencies": { - "@reduxjs/toolkit": "^1.9.6", + "@reduxjs/toolkit": "^1.9.7", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", @@ -18,6 +18,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", + "react-icons": "^5.0.1", "react-query": "^3.39.3", "react-redux": "^8.1.3", "react-router-dom": "^6.16.0", @@ -3728,9 +3729,9 @@ } }, "node_modules/@reduxjs/toolkit": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.6.tgz", - "integrity": "sha512-Gc4ikl90ORF4viIdAkY06JNUnODjKfGxZRwATM30EdHq8hLSVoSrwXne5dd739yenP5bJxAX7tLuOWK5RPGtrw==", + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", + "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", "dependencies": { "immer": "^9.0.21", "redux": "^4.2.1", @@ -16271,6 +16272,14 @@ "react-dom": ">=16" } }, + "node_modules/react-icons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 134284a..82e6d0c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^1.9.6", + "@reduxjs/toolkit": "^1.9.7", "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.5.1", @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", + "react-icons": "^5.0.1", "react-query": "^3.39.3", "react-redux": "^8.1.3", "react-router-dom": "^6.16.0", diff --git a/public/index.html b/public/index.html index 2402be8..0ecb1c9 100644 --- a/public/index.html +++ b/public/index.html @@ -28,6 +28,8 @@ href="https://fonts.googleapis.com/css?family=Poppins" rel="stylesheet" /> + + Activity Leaderboard diff --git a/src/app/api/.gitkeep b/src/app/api/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/api/file.ts b/src/app/api/file.ts index b187ec4..607ec01 100644 --- a/src/app/api/file.ts +++ b/src/app/api/file.ts @@ -1,23 +1,28 @@ import axios, { AxiosResponse } from 'axios'; import { BACKEND_URL } from 'envConstants'; +export interface FileUpload { + message: string; + isSuccessful: boolean; + statusCode: number; +} -export interface FileUpload{ - - message: string, - isSuccessful: boolean, - statusCode: number, - +export interface IconNameRes { + message: string; } + + + + export const uploadIcon = async ( authorizationToken: string, orgName: string, file: File -):Promise> => { +): Promise> => { const url = BACKEND_URL + '/api/protected/file/upload/' + orgName; const formData = new FormData(); formData.append('file', file); @@ -30,16 +35,28 @@ export const uploadIcon = async ( return respnse; }; + + + + export const getIcon = async (authorizationToken: string, orgName: string) => { const url = BACKEND_URL + '/api/protected/file/getIcon/' + orgName; - const response = await axios.get(url, { - headers: { - Authorization: `Bearer ${authorizationToken}`, - }, - }); + const response = await axios.get( + url, + + { + responseType: 'blob', + headers: { + Authorization: `Bearer ${authorizationToken}`, + Accept: '*/*', + }, + } + ); return response; }; + + export const deleteFile = async ( authorizationToken: string, fileName: string @@ -53,3 +70,20 @@ export const deleteFile = async ( }); return respnse; }; + + + + +export const getIconName = async ( + authorizationToken: string, + orgName: string +): Promise> => { + const url = BACKEND_URL + '/api/protected/file/getIconName/' + orgName; + const response = await axios.get(url, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${authorizationToken}`, + }, + }); + return response; +}; diff --git a/src/app/api/githubData.ts b/src/app/api/githubData.ts index 6916685..665dfae 100644 --- a/src/app/api/githubData.ts +++ b/src/app/api/githubData.ts @@ -1,42 +1,40 @@ import axios, { AxiosResponse } from 'axios'; import { BACKEND_URL } from 'envConstants'; - - export interface Contributors { [contributorName: string]: { - issues: number, - pulls: number, - commits:number + issues: number; + pulls: number; + commits: number; }; } export interface ProjectsGithubData { [contributorName: string]: { - issues: number, - pulls: number, - commits:number + issues: number; + pulls: number; + commits: number; }; } - -export interface OrgRank{ - contributors: Contributors +export interface OrgRank { + contributors: Contributors; } - -// Contributors==project issues commits pull -export interface OrgProjectGithubData{ - projects: ProjectsGithubData +export interface ProjectStats { + contributors: Contributors; } +export interface OrgProjectGithubData { + projects: ProjectsGithubData; +} export const getOrgGithubData = async ( authorizationToken: string, orgName: string, monthly: boolean -):Promise> => { +): Promise> => { const url = BACKEND_URL + '/api/protected/github/' + orgName + '?monthly=' + monthly; const respnse = await axios.get(url, { @@ -73,7 +71,7 @@ export const getProjectGithubData = async ( orgName: string, projectName: string, monthly: boolean -) => { +): Promise> => { const url = BACKEND_URL + '/api/protected/github/' + @@ -82,7 +80,7 @@ export const getProjectGithubData = async ( projectName + '?monthly=' + monthly; - const respnse = await axios.get(url, { + const respnse = await axios.get(url, { headers: { Accept: 'application/json', Authorization: `Bearer ${authorizationToken}`, diff --git a/src/app/api/login.ts b/src/app/api/login.ts index 6f1f1c9..2fdfa66 100644 --- a/src/app/api/login.ts +++ b/src/app/api/login.ts @@ -2,15 +2,16 @@ import axios from 'axios'; import { BACKEND_URL } from 'envConstants'; import { AxiosResponse } from 'axios'; - -export interface LoginData{ - token: string, - username: string, - type: string, - id: number +export interface LoginData { + token: string; + username: string; + type: string; + id: number; } -export const login = async (code: string):Promise> => { +export const login = async ( + code: string +): Promise> => { const url = BACKEND_URL + '/api/auth/login'; const respnse = await axios.post( url, diff --git a/src/app/api/organization.ts b/src/app/api/organization.ts index 97ecc2c..24f758f 100644 --- a/src/app/api/organization.ts +++ b/src/app/api/organization.ts @@ -6,26 +6,34 @@ export interface organizationBody { description: string; } - -export interface AllOrgs{ +export interface AllOrgs { organizations: { - id: number, - name: string, - description: string|null - }[] + id: number; + name: string; + description: string | null; + }[]; } -export interface Projects{ - [ProjectName: string]:{ - archeive:boolean, - bookmark: boolean - } +export interface Projects { + [ProjectName: string]: { + archeive: boolean; + bookmark: boolean; + }; } -export interface OrgProjects{ - projects: Projects +export interface OrgProjects { + projects: Projects; } - +export interface Workspace { + id: number; + name: string; + description: string; +} +interface OrgMembers { + members: { + [username: string]: string; + }; +} export const deleteOrg = async ( authorizationToken: string, @@ -116,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: { @@ -179,10 +187,10 @@ export const setBookmarkStatus = async ( export const getOrgMembers = async ( authorizationToken: string, orgName: string -) => { +): Promise> => { const url = BACKEND_URL + '/api/protected/org/getMembers/' + orgName; - const respnse = await axios.get(url, { + const respnse = await axios.get(url, { headers: { Accept: 'application/json', Authorization: `Bearer ${authorizationToken}`, @@ -194,7 +202,7 @@ export const getOrgMembers = async ( export const getOrgProjects = async ( authorizationToken: string, orgName: string -):Promise> => { +): Promise> => { const url = BACKEND_URL + '/api/protected/org/getProjects/' + orgName; const respnse = await axios.get(url, { headers: { @@ -205,9 +213,12 @@ export const getOrgProjects = async ( return respnse; }; -export const getOrg = async (authorizationToken: string, orgName: string) => { - const url = BACKEND_URL + '/api/protected/org/getProjects/' + orgName; - const respnse = await axios.get(url, { +export const getOrg = async ( + authorizationToken: string, + orgName: string +): Promise> => { + const url = BACKEND_URL + '/api/protected/org/getOrg/' + orgName; + const respnse = await axios.get(url, { headers: { Accept: 'application/json', Authorization: `Bearer ${authorizationToken}`, @@ -216,7 +227,9 @@ export const getOrg = async (authorizationToken: string, orgName: string) => { return respnse; }; -export const getAllOrgs = async (authorizationToken: string): Promise> => { +export const getAllOrgs = async ( + authorizationToken: string +): Promise> => { const url = BACKEND_URL + '/api/protected/org/getAllOrg'; const respnse = await axios.get(url, { headers: { diff --git a/src/app/api/project.ts b/src/app/api/project.ts index e604a3d..b0ae917 100644 --- a/src/app/api/project.ts +++ b/src/app/api/project.ts @@ -7,17 +7,18 @@ export interface projectBody { link: string; } -export interface GetProject{ - id: number, - name: string, - description: string +export interface GetProject { + id: number; + name: string; + description: string; + link: string; } -export interface Member{ - [key: string]:string +export interface Member { + [key: string]: string; } -export interface ProjectMembers{ - members:Member +export interface ProjectMembers { + members: Member; } export const addProject = async ( @@ -159,7 +160,7 @@ export const getProject = async ( authorizationToken: string, projectName: string, orgName: string -):Promise> => { +): Promise> => { const url = BACKEND_URL + '/api/protected/project/getProject/' + @@ -179,7 +180,7 @@ export const getMembers = async ( authorizationToken: string, projectName: string, orgName: string -) : Promise>=> { +): Promise> => { const url = BACKEND_URL + '/api/protected/project/getMembers/' + diff --git a/src/app/api/user.ts b/src/app/api/user.ts index 5b6fbfd..677e4f0 100644 --- a/src/app/api/user.ts +++ b/src/app/api/user.ts @@ -1,38 +1,48 @@ -import axios,{AxiosResponse} from 'axios'; +import axios, { AxiosResponse } from 'axios'; import { BACKEND_URL } from 'envConstants'; - - - -export interface UserData{ - message: string +export interface UserData { + message: string; } +export interface UserOrgs { + userOrgs: UserOrgDetails; +} +export interface UserOrgDetails { + [key: string]: { + bookmark: string; + role: string; + archeive: string; + }; +} -export interface AllUserData{ +export interface AllUserData { users: { - id: number, - username: string - }[] + id: number; + username: string; + }[]; } -export const getUser= async (authorizationToken: string):Promise> => { +export const getUser = async ( + authorizationToken: string +): Promise> => { const url = BACKEND_URL + '/api/protected/user/getUser'; - const respnse = await axios.get(url, { - headers: { - Accept: 'application/json', - - Authorization: `Bearer ${authorizationToken}`, - }, - }); - - return respnse; + const respnse = await axios.get(url, { + headers: { + Accept: 'application/json', + + Authorization: `Bearer ${authorizationToken}`, + }, + }); + return respnse; }; -export const getAllUser = async (authorizationToken: string):Promise> => { +export const getAllUser = async ( + authorizationToken: string +): Promise> => { const url = BACKEND_URL + '/api/protected/user/all'; const respnse = await axios.get(url, { headers: { @@ -71,7 +81,7 @@ export const setOrgArcheiveStatus = async ( const respnse = await axios.put( url, { - bookmarkStatus: status, + archeiveStatus: status, }, { headers: { @@ -80,18 +90,16 @@ export const setOrgArcheiveStatus = async ( }, } ); + return respnse; }; export const getUserOrgs = async ( authorizationToken: string, username: string -) => { - const url = - BACKEND_URL + - '/api/protected/user/setArcheiveStatus/getUserOrgs/' + - username; - const respnse = await axios.get(url, { +): Promise> => { + const url = BACKEND_URL + '/api/protected/user/getUserOrgs/' + username; + const respnse = await axios.get(url, { headers: { Accept: 'application/json', Authorization: `Bearer ${authorizationToken}`, diff --git a/src/app/components/buttonBar/index.tsx b/src/app/components/buttonBar/index.tsx index 826c4f1..4945f7e 100644 --- a/src/app/components/buttonBar/index.tsx +++ b/src/app/components/buttonBar/index.tsx @@ -1,27 +1,39 @@ -import React from 'react'; +import React, { useState } from 'react'; import TimeRangeSwitch from 'app/components/timeRangeSwitch'; import './index.scss'; -const ButtonBar = () => { +import { GetProject } from 'app/api/project'; +import { useNavigate } from 'react-router-dom'; +interface Props { + weekly: boolean; + setWeekly: (week: boolean) => void; + project: GetProject | null; + workspaceName: string | undefined; +} +const ButtonBar: React.FC = ({ + weekly, + setWeekly, + project, + workspaceName, +}) => { + const navigate = useNavigate(); return (
- - + +
-

Appetizer

-

- Lorem ipsum dolor sit amet consectetur adipisicing elit. Libero, - officiis aut. Expedita doloremque itaque quae perspiciatis ratione aut - tempora mollitia obcaecati saepe. Quam sapiente odio molestias eos at - nisi dolorum. At corporis dolore, explicabo et eveniet facilis amet sit - reiciendis quae officia quam vel quisquam in blanditiis ad, id - praesentium voluptates ipsum! Sunt magni placeat maiores libero non - fugiat veniam? Voluptatibus blanditiis ipsum enim eum, exercitationem - culpa quidem unde ex consectetur dignissimos quae libero cumque! Quis - eveniet unde, necessitatibus sit corrupti eius saepe nobis magnam et - tempore architecto libero dolorum? -

+ {project && ( + <> +

{project.name}

+

{project.description}

+ + )}
); }; diff --git a/src/app/components/firstVisit/index.scss b/src/app/components/firstVisit/index.scss new file mode 100644 index 0000000..c215fb5 --- /dev/null +++ b/src/app/components/firstVisit/index.scss @@ -0,0 +1,32 @@ +.modal-footer { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 0.5rem; + font-weight: 600; + a { + text-decoration: none; + } +} + +.firstvisit-btn { + background-color: #402aa4; + border: none; + color: white; + padding: 0.5rem 1rem; + border-radius: 5rem; + border: 1px solid #402aa4; + font-size: 1rem; +} + +.firstvisit-btn:hover { + background-color: whitesmoke; + border: none; + color: #402aa4; + padding: 0.5rem 1rem; + border-radius: 5rem; + border: 1px solid #402aa4; + font-size: 1rem; +} diff --git a/src/app/components/firstVisit/index.tsx b/src/app/components/firstVisit/index.tsx new file mode 100644 index 0000000..8f6822a --- /dev/null +++ b/src/app/components/firstVisit/index.tsx @@ -0,0 +1,42 @@ +import { useEffect, useState } from 'react'; +import Popup from '../popup'; +import './index.scss'; + +export default function FirstVisit() { + const [show, setShow] = useState(false); + useEffect(() => { + const visit = localStorage.getItem('visit'); + if (visit !== 'true') { + localStorage.setItem('visit', 'true'); + + setShow(true); + } + }, []); + + return ( + <> + {show && ( + setShow(false)} + footer={ +
+

+ Google Summer of Code is a global, online program focused on + bringing new contributors into open source software development. +

+ + Visit GSOC + +
+ } + popup_zindex={{ overlay: 3000, modal: 4000 }} + /> + )} + + ); +} diff --git a/src/app/components/popup/index.scss b/src/app/components/popup/index.scss new file mode 100644 index 0000000..7ddab5a --- /dev/null +++ b/src/app/components/popup/index.scss @@ -0,0 +1,70 @@ +.popup-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); +} + +.popup-modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + border-radius: 10px; + max-width: 500px; + width: 100%; + max-height: 67vh; + overflow-y: auto; + color: black; +} + +.popup-title { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid black; + padding: 0.5em 1em 0.5em 1em; + background-color: #402aa4; + color: white; +} + +.popup-body { + text-align: center; + padding: 0.5em 1em 0.5em 1em; +} + +.popup-footer { + display: flex; + flex-direction: row-reverse; + margin: 0.6em; +} + +.close-btn { + background: none; + border: none; + padding: 0.1em; + color: white; +} + +.modal-btn { + width: 30px; + height: 30px; + background-color: transparent; + border: none; + color: white; + border: 1px solid white; + border-radius: 50%; +} + +.modal-btn:hover { + width: 30px; + height: 30px; + background-color: white; + border: none; + color: #402aa4; + border: 1px solid #402aa4; + border-radius: 50%; +} diff --git a/src/app/components/popup/index.tsx b/src/app/components/popup/index.tsx new file mode 100644 index 0000000..49e05c4 --- /dev/null +++ b/src/app/components/popup/index.tsx @@ -0,0 +1,85 @@ +import './index.scss'; +import ReactPortal from '../reactPortal'; +import { ReactNode } from 'react'; +type _POPUP_PROPS = { + onClose?: () => void; + onSubmit?: () => void; + title: string | ReactNode; + type?: 'success' | 'error'; + submit?: string; + content?: string | ReactNode; + footer?: ReactNode; + popup_zindex?: { + overlay: number; + modal: number; + }; +}; + +const Popup = ({ + title, + content, + type, + onClose, + onSubmit, + submit, + footer, + popup_zindex, +}: _POPUP_PROPS) => { + return ( + +
+
+
+ {typeof title === 'string' ? ( + {title} + ) : ( +
{title}
+ )} + {onClose && ( + + )} +
+ {content &&
{content}
} +
+ {onSubmit && ( + + )} +
{footer}
+
+
+ + ); +}; + +export default Popup; diff --git a/src/app/components/reactPortal/index.tsx b/src/app/components/reactPortal/index.tsx new file mode 100644 index 0000000..a855a0a --- /dev/null +++ b/src/app/components/reactPortal/index.tsx @@ -0,0 +1,14 @@ +import { ReactNode, useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; + +/** + * Ensures that document.body is available + */ +export default function ReactPortal({ children }: { children: ReactNode }) { + const [mounted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + + return <>{mounted && createPortal(<>{children}, document.body)}; +} diff --git a/src/app/components/search/index.tsx b/src/app/components/search/index.tsx index 1c6e725..f568707 100644 --- a/src/app/components/search/index.tsx +++ b/src/app/components/search/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { useDispatch } from 'react-redux'; -import { searchAction } from 'features/home/slices/projectSearchSlice'; +import { searchAction } from 'features/workspace/slices/projectSearchSlice'; import search_icon from 'app/assets/images/search_icon.svg'; import './index.scss'; @@ -12,7 +12,7 @@ const SearchBar = () => { }; const handleSearch = (e: React.FormEvent) => { e.preventDefault(); - if (search !== '') dispatch(searchAction(search)); + dispatch(searchAction(search)); }; return ( diff --git a/src/app/components/timeRangeSwitch/index.scss b/src/app/components/timeRangeSwitch/index.scss index 71d739e..52062b9 100644 --- a/src/app/components/timeRangeSwitch/index.scss +++ b/src/app/components/timeRangeSwitch/index.scss @@ -1,3 +1,10 @@ +.timerange-cont { + display: flex; + justify-content: center; + height: 100%; + align-items: center; +} + .timerange-cont button { border: none; outline: none; diff --git a/src/app/components/timeRangeSwitch/index.tsx b/src/app/components/timeRangeSwitch/index.tsx index edfa50e..6e62dec 100644 --- a/src/app/components/timeRangeSwitch/index.tsx +++ b/src/app/components/timeRangeSwitch/index.tsx @@ -1,24 +1,34 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { timeRangeModel } from 'features/project/components/contributorCard/types'; import { weekAction, monthAction } from './timeRangeSlice'; import './index.scss'; +import { clearConfigCache } from 'prettier'; -const TimeRangeSwitch = () => { - const dispatch = useDispatch(); - const isWeekly = useSelector((state: timeRangeModel) => state.isWeekly.value); +interface Props { + weekly: boolean; + setWeekly: (bool: boolean) => void; +} +const TimeRangeSwitch: React.FC = ({ weekly, setWeekly }) => { + // const dispatch = useDispatch(); + // const isWeekly = useSelector((state: timeRangeModel) => state.isWeekly.value); + useEffect(()=>{ + + },[weekly, setWeekly]) return (
diff --git a/src/app/constants/api.ts b/src/app/constants/api.ts index 1dff1ff..a81f0db 100644 --- a/src/app/constants/api.ts +++ b/src/app/constants/api.ts @@ -1 +1 @@ -export const AVATAR_URL="https://api.multiavatar.com" +export const AVATAR_URL = 'https://api.multiavatar.com'; diff --git a/src/app/context/user/userContext.tsx b/src/app/context/user/userContext.tsx new file mode 100644 index 0000000..0d7da6e --- /dev/null +++ b/src/app/context/user/userContext.tsx @@ -0,0 +1,13 @@ +import { UserOrgs } from 'app/api/user'; +import { createContext } from 'react'; + +export interface UserContextType { + username: String | null; + setUsername: (name: String) => void; + userOrgs: UserOrgs | null; + setUserOrgs: (user_Orgs: UserOrgs) => void; +} + +const UserContext = createContext(undefined); + +export default UserContext; diff --git a/src/app/context/user/userState.tsx b/src/app/context/user/userState.tsx new file mode 100644 index 0000000..9648558 --- /dev/null +++ b/src/app/context/user/userState.tsx @@ -0,0 +1,22 @@ +import { FC, ReactNode, useState } from 'react'; +import UserContext from './userContext'; +import { UserOrgs } from 'app/api/user'; + +interface Props { + children: ReactNode; +} + +const UserState: FC = ({ children }) => { + const [username, setUsername] = useState(null); + const [userOrgs, setUserOrgs] = useState(null); + + return ( + + {children} + + ); +}; + +export default UserState; diff --git a/src/app/index.scss b/src/app/index.scss index 058bdb6..f49efde 100644 --- a/src/app/index.scss +++ b/src/app/index.scss @@ -37,3 +37,8 @@ a:hover { .bd-white { border: 1px solid white; } + + +.pointer{ + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/index.tsx b/src/app/index.tsx index c71f936..3a9bf08 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -1,15 +1,52 @@ -import React from 'react'; import Navbar from 'app/components/navbar'; import BasicRoutes from 'app/routes/BasicRoutes'; import './index.scss'; -import { Toaster } from 'react-hot-toast'; +import toast, { Toaster } from 'react-hot-toast'; +import { getUser } from './api/user'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useQuery } from 'react-query'; +import { useContext } from 'react'; +import UserContext from './context/user/userContext'; + function App() { + const navigate = useNavigate(); + const location = useLocation(); + const currentPath = location.pathname; + const userContext = useContext(UserContext); + const token = localStorage.getItem('token'); + const checklogin = async () => { + if (token != null) { + try { + const userData = await getUser(token); + userContext?.setUsername(userData.data.message); + if (currentPath == '/login') { + navigate('/'); + } + } catch (e) { + localStorage.removeItem('token'); + if (currentPath != '/login') { + toast.error('Session expired'); + navigate('/login'); + } + } + } else { + if (currentPath != '/login') { + toast.error('Not authenticated'); + navigate('/login'); + } + } + }; + const {} = useQuery('login', checklogin, { + enabled: true, + staleTime: Infinity, + }); + return ( - <> +
- - + +
); } diff --git a/src/app/routes/BasicRoutes.tsx b/src/app/routes/BasicRoutes.tsx index d9f58ca..576a76f 100644 --- a/src/app/routes/BasicRoutes.tsx +++ b/src/app/routes/BasicRoutes.tsx @@ -1,22 +1,57 @@ -import React from 'react'; + import { Routes, Route } from 'react-router-dom'; -import Home from 'features/home'; import ProjectPage from 'features/project'; import AddProject from 'features/AddProject'; import Error from 'features/Error'; import WorkspaceView from 'features/workspace-view'; import Login from 'features/login'; import AddWorkspace from 'features/AddWorkspace'; +import Workspace from 'features/workspace'; + +import EditWorkspace from 'features/EditWorkspace'; +import EditProject from 'features/EditProject'; +import WorkspaceMembers from 'features/workspace-members '; +import WorkspaceAddMember from 'features/WorkspaceAddMember'; +import ProjectMembers from 'features/project-members '; +import ProjectAddMember from 'features/ProjectAddMember '; +import FirstVisit from '../components/firstVisit'; + const BasicRoutes = () => { return ( - } /> - } /> - } /> - } /> + } /> + } /> + } + /> + } /> + } /> } /> } /> + } /> + } + /> + } + /> + } + /> + } + /> + } + /> } /> + {/* } /> */} ); }; diff --git a/src/app/state/action-creators/orgActions.ts b/src/app/state/action-creators/orgActions.ts index 6f3b308..6c7caa4 100644 --- a/src/app/state/action-creators/orgActions.ts +++ b/src/app/state/action-creators/orgActions.ts @@ -1,23 +1,17 @@ -import { Dispatch } from "redux" -import { Organization } from "../reducers/orgReducers" +import { Dispatch } from 'redux'; +import { Organization } from '../reducers/orgReducers'; +export const AddOrganization = (org: Organization) => { + return (dispatch: Dispatch) => { + dispatch({ type: 'add', payload: org }); + }; +}; - - - -export const AddOrganization= (org:Organization)=>{ - return(dispatch:Dispatch)=>{ - dispatch({ type:'add', - payload:org}) - } -} - -export const DeleteOrg=(org:Organization)=>{ - return(dispatch:Dispatch)=>{ - dispatch({ - type:'delete', - payload:org - }) - } -} - +export const DeleteOrg = (org: Organization) => { + return (dispatch: Dispatch) => { + dispatch({ + type: 'delete', + payload: org, + }); + }; +}; diff --git a/src/app/state/action-creators/projectActions.ts b/src/app/state/action-creators/projectActions.ts index 1f2018a..171e738 100644 --- a/src/app/state/action-creators/projectActions.ts +++ b/src/app/state/action-creators/projectActions.ts @@ -1,22 +1,23 @@ -import { Dispatch } from "redux" +import { Dispatch } from 'redux'; +export const addProject = ( + project: [string, { archieve: boolean; bookmark: boolean }] +) => { + return (dispatch: Dispatch) => { + dispatch({ + type: 'add', + payload: project, + }); + }; +}; -export const addProject = (project:[string,{archieve:boolean, bookmark:boolean}])=>{ - - return(dispatch:Dispatch)=>{ - dispatch({ - type: 'add', - payload: project - }) - } -} - -export const deleteProject= (project:[string,{archeive:boolean, bookmark:boolean}])=>{ - - return(dispatch:Dispatch)=>{ - dispatch({ - type:'delete', - payload: project - }) - } -} \ No newline at end of file +export const deleteProject = ( + project: [string, { archeive: boolean; bookmark: boolean }] +) => { + return (dispatch: Dispatch) => { + dispatch({ + type: 'delete', + payload: project, + }); + }; +}; diff --git a/src/app/state/action-creators/usersActions.ts b/src/app/state/action-creators/usersActions.ts index b00d4dc..e7078bc 100644 --- a/src/app/state/action-creators/usersActions.ts +++ b/src/app/state/action-creators/usersActions.ts @@ -17,7 +17,3 @@ export const setAllUsernames = (usernames: string[]) => { }); }; }; - - - - diff --git a/src/app/state/reducers/index.ts b/src/app/state/reducers/index.ts index 36506a4..564b80f 100644 --- a/src/app/state/reducers/index.ts +++ b/src/app/state/reducers/index.ts @@ -1,7 +1,7 @@ import { combineReducers } from 'redux'; import { setAllUsernamesReducer, setUsernameReducer } from './usersReducers'; import timeRangeReducer from 'app/components/timeRangeSwitch/timeRangeSlice'; -import searchReducer from 'features/home/slices/projectSearchSlice'; +import searchReducer from 'features/workspace/slices/projectSearchSlice'; import { orgReducer } from './orgReducers'; export const reducers = combineReducers({ @@ -9,7 +9,7 @@ export const reducers = combineReducers({ searchKeyword: searchReducer, setUsername: setUsernameReducer, setAllUsernames: setAllUsernamesReducer, - organization: orgReducer + organization: orgReducer, }); -export type RootState= ReturnType +export type RootState = ReturnType; diff --git a/src/app/state/reducers/orgReducers.ts b/src/app/state/reducers/orgReducers.ts index c0689aa..a47ac3d 100644 --- a/src/app/state/reducers/orgReducers.ts +++ b/src/app/state/reducers/orgReducers.ts @@ -1,29 +1,25 @@ -import { act } from "react-dom/test-utils" - -export interface Organization{ - name: string, - description: string|null +import { act } from 'react-dom/test-utils'; +export interface Organization { + name: string; + description: string | null; } - -export const orgReducer=(state: Organization[]=[], action:{ - type:string, - payload: Organization -})=>{ - - switch(action.type){ - case 'add': - return state.concat(action.payload) - - case 'delete': - return state.filter(elem=>elem!=action.payload) - - default: - return state - } - -} - - - +export const orgReducer = ( + state: Organization[] = [], + action: { + type: string; + payload: Organization; + } +) => { + switch (action.type) { + case 'add': + return state.concat(action.payload); + + case 'delete': + return state.filter((elem) => elem != action.payload); + + default: + return state; + } +}; diff --git a/src/app/state/store.ts b/src/app/state/store.ts index 6471a84..a4660c2 100644 --- a/src/app/state/store.ts +++ b/src/app/state/store.ts @@ -1,6 +1,6 @@ import { legacy_createStore as createStore } from 'redux'; -import {reducers} from './reducers'; +import { reducers } from './reducers'; const store = createStore(reducers, {}); diff --git a/src/envConstants.ts b/src/envConstants.ts index efcb05a..81d836e 100644 --- a/src/envConstants.ts +++ b/src/envConstants.ts @@ -1,3 +1,6 @@ -export const CLIENT_ID = '149d2857118e05e729a8'; -export const BACKEND_URL = 'http://localhost:8080'; -export const AVATAR_API= "w9zrqHdDa4MsYB"; \ No newline at end of file +export const CLIENT_ID = process.env.CLIENT_ID; +export const BACKEND_URL = process.env.BACKEND_URL; +export const AVATAR_API = process.env.AVATAR_API; + +// http://13.233.127.61:8080 +// https://leaderboard-java.mdgspace.org \ No newline at end of file diff --git a/src/features/AddProject/index.tsx b/src/features/AddProject/index.tsx index da553f2..b8f3464 100644 --- a/src/features/AddProject/index.tsx +++ b/src/features/AddProject/index.tsx @@ -1,61 +1,41 @@ -import React, { ChangeEvent, useState } from 'react'; +import React, { ChangeEvent, useContext, useEffect, useState } from 'react'; import './index.scss'; import tick from '../../app/assets/images/tick.png'; -import { useNavigate } from 'react-router-dom'; -import { getUser } from 'app/api/user'; +import { useNavigate, useParams } from 'react-router-dom'; import toast from 'react-hot-toast'; -import { useQuery } from 'react-query'; -import axios from 'axios'; import { addProject } from 'app/api/project'; +import { Projects, getOrgProjects } from 'app/api/organization'; + + const AddProject = () => { const navigate = useNavigate(); const token = localStorage.getItem('token'); - const orgName = 'orgName'; + const { spaceName } = useParams(); const [name, setName] = useState(null); const [description, setDescription] = useState(null); const [link, setLink] = useState(null); - const [validLink, setValidLink] = useState(false); + const [orgProject, setOrgProjects] = useState(null); - const [validName, setValidName] = useState(false); - - const checkLogin = async () => { - if (token != null) { - const userData = await getUser(token); - return userData.data; - } else { - toast.error('Not authorized'); - navigate('/login'); + const fetchData = async () => { + if (token && spaceName) { + try { + const res = await getOrgProjects(token, spaceName); + setOrgProjects(res.data.projects); + } catch (e) {} } }; - const { data, isError } = useQuery({ - queryFn: () => checkLogin(), - queryKey: 'checkLogin', - }); - - if (isError) { - toast.error('Session Expired'); - navigate('/login'); - } + useEffect(() => { + fetchData(); + }, [spaceName]); const linkChange = async (event: ChangeEvent) => { setLink(event.target.value); - - if (isGitHubRepositoryLink(event.target.value)) { - try { - const response = await axios.get(event.target.value); - setValidLink(true); - return; - } catch (e) {} - } - - setValidLink(false); }; const nameChange = async (event: ChangeEvent) => { setName(event.target.value); - setValidName(true); }; function isGitHubRepositoryLink(link: string): boolean { @@ -67,39 +47,50 @@ const AddProject = () => { return githubRepoPattern.test(link); } + function isValidName(input: string): boolean { + // Regular expression to match only alphanumeric characters, hyphens, and underscores + const regex = /^[a-zA-Z0-9-_]+$/; + + // Test if the input string matches the regular expression + return regex.test(input); + } + + const isUnique = (name: string) => { + if (orgProject && name in orgProject) { + return false; + } + return true; + }; + const SubmitHandler = async () => { if ( + spaceName && token && name && - validName && - validLink && - description && link && - description?.length > 3 + isValidName(name) && + isGitHubRepositoryLink(link) && + description && + description?.length < 200 ) { - try { - const res = await addProject(token, orgName, { + const func = async () => { + const res = await addProject(token, spaceName, { name: name, description: description, link: link, }); - } catch (e) { - toast.error('Error while saving'); - } + navigate(`/workspace/${spaceName}`); + }; + toast.promise(func(), { + loading: 'Saving Project', + success: Project saved, + error: Could not save, + }); + } else { + toast.error('Invalid inputs'); } - - toast.error('Invalid inputs'); }; - - toast.promise( - SubmitHandler(),{ - loading: 'Saving Project', - success: Project saved, - error:Could not save - } - ) - return (
@@ -111,6 +102,9 @@ const AddProject = () => { onChange={nameChange} value={name ? name : ''} /> + {!name ? 'Name feild should not be empty' : <>} + {name && !isValidName(name) && 'Not a valid name'} + {name && !isUnique(name) && 'This project name already exists'}
Project link
{ onChange={linkChange} placeholder='Github link of project' /> + {!link ? 'Link feild should not be empty' : <>} + {link && + !isGitHubRepositoryLink(link) && + 'Not a valid github repository link'}
Description
{ } placeholder='Details about project' /> + {!description ? 'Description feild should not be empty' : <>} + {description && + description.length >= 200 && + 'Description length should not be greater than 200'} -
diff --git a/src/features/AddWorkspace/index.scss b/src/features/AddWorkspace/index.scss new file mode 100644 index 0000000..f6f5ed5 --- /dev/null +++ b/src/features/AddWorkspace/index.scss @@ -0,0 +1,162 @@ +$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/AddWorkspace/index.tsx b/src/features/AddWorkspace/index.tsx index 92a1009..9a5814d 100644 --- a/src/features/AddWorkspace/index.tsx +++ b/src/features/AddWorkspace/index.tsx @@ -1,34 +1,32 @@ -import { getAllUser, getUser } from 'app/api/user'; -import React, { ChangeEvent, useState } from 'react'; +import { getAllUser } from 'app/api/user'; +import { 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, getAllOrgs } from 'app/api/organization'; +import { addOrg, addOrgMembers, getAllOrgs } from 'app/api/organization'; import { uploadIcon } from 'app/api/file'; -import { useSelector } from 'react-redux'; -import { Organization } from 'app/state/reducers/orgReducers'; -import { RootState } from 'app/state/reducers'; - +import './index.scss'; +import UserContext from 'app/context/user/userContext'; +import { AVATAR_URL } from 'app/constants/api'; +import { AVATAR_API } from 'envConstants'; const AddWorkspace = () => { 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 [memberName, setMemberName] = useState(null); const [users, setUsers] = useState([]); const [orgs, setOrgs] = useState([]); - const orgState= useSelector((state:RootState)=>state.organization); - - const dataFetch = async () => { try { if (token) { @@ -50,30 +48,9 @@ const AddWorkspace = () => { } catch (e) {} }; - const checklogin = async () => { - if (token != null) { - const userData = await getUser(token); - return userData.data - } else { - toast.error("Not authorized") - navigate('/login'); - } - }; - - const { data,isError } = useQuery({ - queryFn: () => checklogin(), - queryKey: 'checkLogin', - }); - - if (isError) { - toast.error("Session expired") - navigate('/login'); - } - - const {} = useQuery({ - queryFn: () => dataFetch(), - queryKey: 'allUsersAndAllOrgs', - }); + useEffect(() => { + dataFetch(); + }, []); const allowedFieTypes = ['image/jpeg', 'image/jpg', 'image/png']; @@ -92,115 +69,213 @@ const AddWorkspace = () => { function valid_name(str: string): boolean { // Define a regular expression for special characters (excluding letters, digits, and spaces) - const specialCharacters = /[^a-zA-Z0-9\s]/; + const specialCharacters = /^[a-zA-Z0-9_-]+$/; // Check if the string contains any special characters - return ( - specialCharacters.test(str) && - !str.endsWith('/userspace') && - !orgs.includes(str) - - ); + return specialCharacters.test(str) && !str.endsWith('-userspace'); } - const isNotOrgName= (orgName:string)=>{ - orgState.forEach(el=>{ - if(el.name==orgName){ - return false - } - }) - return true + function isUniqueName(str: string): boolean { + return !orgs.includes(str); } const handleNameChange = (event: ChangeEvent) => { SetName(event.target.value); - - - - setValidName(() => valid_name(event.target.value)&&isNotOrgName(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!=data?.message){ - setMembers([...members,memberName]) - setMemberName(null) + const addMembers = () => { + if (memberName) { + if ( + users.includes(memberName) && + memberName != userContext?.username && + !members.includes(memberName) + ) { + setMembers([...members, memberName]); + setMemberName(null); } } - } - + }; - const SubmitHandler=async():Promise=>{ + const removeMembers = (member: string) => { + const indexToRemove = members.indexOf(member); + if (indexToRemove !== -1) { + const updatedMembers = [ + ...members.slice(0, indexToRemove), + ...members.slice(indexToRemove + 1), + ]; - if(validName&&description&&token&&name){ - - 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){ - - } - navigate("/workspace-view") - } - - toast.promise( - func(),{ - loading:'Saving...', - success: Workspace Saved, - error: Could not save., + 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); } - ) - - - }else{ - toast.error('Invalid inputs') + } 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 ( -
- - -

Selected File: {selectedFile?.name}

- - - - )=>{setMemberName(e.target.value)}} - placeholder='Enter membername' - /> - -

{members}

- +
+
+
+ +
+ + +

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}

{' '} + +
+ ); + })} +
+ + +
); }; diff --git a/src/features/EditProject/index.scss b/src/features/EditProject/index.scss new file mode 100644 index 0000000..f1e4f07 --- /dev/null +++ b/src/features/EditProject/index.scss @@ -0,0 +1,75 @@ +.add-project-container { + width: 70vw; + height: 76.5vh; + margin: 60px auto; + background: linear-gradient(110.51deg, #141432 0.9%, #2a2a4b 101.51%); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.add-project-form input { + box-sizing: border-box; + + /* Auto layout */ + border: 0.8px solid #402aa4; + border-radius: 14px; + background: linear-gradient(110.51deg, #141432 0.9%, #2a2a4b 101.51%); + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 20px 100px 20px 30px; + margin: 20px 0; + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-size: 20px; + line-height: 30px; + /* identical to box height */ + + color: rgba(173, 173, 255, 0.75); +} + +.input-title { + font-family: 'Poppins'; + font-style: normal; + font-weight: 700; + font-size: 20px; + line-height: 30px; + /* identical to box height */ + + color: #8181ff; +} + +.add-project-btn { + position: absolute; + right: 66px; + bottom: 44px; + font-family: 'Poppins'; + font-style: normal; + font-weight: 400; + font-size: 15px; + line-height: 22px; + /* identical to box height */ + + display: flex; + align-items: center; + display: flex; + flex-direction: row; + align-items: flex-start; + padding: 10px 16px; + gap: 2px; + flex: none; + order: 1; + flex-grow: 0; + background: #402aa4; + border-radius: 14px; + color: #ffffff; +} + +.add-project-btn img { + padding-top: 3px; + padding-right: 4px; +} diff --git a/src/features/EditProject/index.tsx b/src/features/EditProject/index.tsx new file mode 100644 index 0000000..3fc9ad5 --- /dev/null +++ b/src/features/EditProject/index.tsx @@ -0,0 +1,154 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import './index.scss'; +import tick from '../../app/assets/images/tick.png'; +import { useNavigate, useParams } from 'react-router-dom'; +import toast from 'react-hot-toast'; +import { + getProject, + updateProject, +} from 'app/api/project'; +import UserContext from 'app/context/user/userContext'; +import { Projects, getOrgProjects } from 'app/api/organization'; + + + +const EditProject = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const [name, setName] = useState(null); + const [description, setDescription] = useState(null); + const [link, setLink] = useState(null); + const [orgProject, setOrgProjects] = useState(null); + + const { spaceName, projectName } = useParams(); + + const fetchData = async () => { + if (token && spaceName && projectName) { + try { + const res = await getOrgProjects(token, spaceName); + setOrgProjects(res.data.projects); + const proRes = await getProject(token, projectName, spaceName); + setName(projectName); + setDescription(proRes.data.description); + setLink(proRes.data.link); + } catch (e) {} + } + }; + + useEffect(() => { + fetchData(); + }, [spaceName]); + + const linkChange = async (event: ChangeEvent) => { + setLink(event.target.value); + }; + + const nameChange = async (event: ChangeEvent) => { + setName(event.target.value); + }; + + function isGitHubRepositoryLink(link: string): boolean { + // Define a regular expression pattern for a GitHub repository URL + const githubRepoPattern = + /^https:\/\/github\.com\/[a-zA-Z0-9-]+\/[a-zA-Z0-9-]+(\.git)?$/; + + // Check if the provided link matches the pattern + return githubRepoPattern.test(link); + } + + function isValidName(input: string): boolean { + // Regular expression to match only alphanumeric characters, hyphens, and underscores + const regex = /^[a-zA-Z0-9-_]+$/; + + // Test if the input string matches the regular expression + return regex.test(input); + } + + const isUnique = (name: string) => { + if (orgProject && name in orgProject) { + return false; + } + return true; + }; + + const SubmitHandler = async () => { + if ( + spaceName && + token && + name && + link && + projectName && + isValidName(name) && + isGitHubRepositoryLink(link) && + description && + description?.length < 200 + ) { + const func = async () => { + const res = await updateProject(token, projectName, spaceName, { + name: name, + description: description, + link: link, + }); + navigate(`/workspace/${spaceName}`); + }; + toast.promise(func(), { + loading: 'Saving Project', + success: Project saved, + error: Could not save, + }); + } else { + toast.error('Invalid inputs'); + } + }; + + return ( +
+
+
+
Name
+ + {!name ? 'Name feild should not be empty' : <>} + {name && !isValidName(name) && 'Not a valid name'} + {name && + name != projectName && + !isUnique(name) && + 'This project name already exists'} +
Project link
+ + {!link ? 'Link feild should not be empty' : <>} + {link && + !isGitHubRepositoryLink(link) && + 'Not a valid github repository link'} +
Description
+ ) => + setDescription(event.target.value) + } + placeholder='Details about project' + /> + {!description ? 'Description feild should not be empty' : <>} + {description && + description.length >= 200 && + 'Description length should not be greater than 200'} +
+ +
+
+ ); +}; + +export default EditProject; diff --git a/src/features/EditWorkspace/index.scss b/src/features/EditWorkspace/index.scss new file mode 100644 index 0000000..f6f5ed5 --- /dev/null +++ b/src/features/EditWorkspace/index.scss @@ -0,0 +1,162 @@ +$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/EditWorkspace/index.tsx b/src/features/EditWorkspace/index.tsx new file mode 100644 index 0000000..f708b96 --- /dev/null +++ b/src/features/EditWorkspace/index.tsx @@ -0,0 +1,212 @@ +import { getAllUser, getUser } from 'app/api/user'; +import { ChangeEvent, useContext, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import toast from 'react-hot-toast'; +import { + + getAllOrgs, + getOrg, + updateOrg, +} from 'app/api/organization'; +import { uploadIcon } from 'app/api/file'; + +import './index.scss'; +import UserContext from 'app/context/user/userContext'; + +const EditWorkspace = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const { spaceName } = useParams(); + + 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 [users, setUsers] = useState([]); + const [orgs, setOrgs] = useState([]); + + const dataFetch = async () => { + try { + if (token && spaceName) { + 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); + } + if (token && spaceName) { + SetName(spaceName); + setUniqueName(true); + setValidName(true); + } + if (token && spaceName) { + const Org = await getOrg(token, spaceName); + setDiscription(Org.data.description); + } + } 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 SubmitHandler = async (): Promise => { + if ( + description && + token && + name && + spaceName && + validName && + uniqueName && + validDescription + ) { + const func = async (): Promise => { + const dataRes = await updateOrg(token, spaceName, { + name: name, + description: description, + }); + + try { + if (selectedFile != null) { + const fileRes = uploadIcon(token, name, selectedFile); + } + } 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

: <>} + {name != spaceName && !validName && name ? ( +

Not a valid name

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

Name already taken. Try another name

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

Description feild should not be empty

: <>} + {!validDescription ? ( +

Characters length should be less than 200

+ ) : ( + <> + )} +
+ + +
+
+ ); +}; + +export default EditWorkspace; diff --git a/src/features/ProjectAddMember /index.scss b/src/features/ProjectAddMember /index.scss new file mode 100644 index 0000000..e47491f --- /dev/null +++ b/src/features/ProjectAddMember /index.scss @@ -0,0 +1,189 @@ +$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; + position: relative; + flex-direction: column; + gap: 1rem; + width: 100%; + .member-search-result { + position: absolute; + border: 2px solid #8989ce; + top: 6rem; + background-color: #1414325b; + gap: 1rem; + width: fit-content; + backdrop-filter: blur(25px); + display: flex; + flex-direction: column; + padding: 1rem 1rem; + z-index: 1000; + p { + color: #8989ce; + width: fit-content; + font-size: 1.25rem; + font-weight: 900; + padding: 0.1rem 1rem; + border-radius: 1rem; + } + P:hover { + color: #8181ff; + cursor: pointer; + background-color: black; + } + } + .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/ProjectAddMember /index.tsx b/src/features/ProjectAddMember /index.tsx new file mode 100644 index 0000000..b08ff09 --- /dev/null +++ b/src/features/ProjectAddMember /index.tsx @@ -0,0 +1,194 @@ + +import { ChangeEvent, useContext, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { AVATAR_API } from 'envConstants'; +import { AVATAR_URL } from 'app/constants/api'; +import toast from 'react-hot-toast'; +import { + getOrgMembers, +} from 'app/api/organization'; + +import './index.scss'; +import UserContext from 'app/context/user/userContext'; +import { addProjectsMembers, getMembers } from 'app/api/project'; + +const ProjectAddMember = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + + const [members, setMembers] = useState([]); + const [memberName, setMemberName] = useState(null); + + const { spaceName, projectName } = useParams(); + const [projectMembers, setProjectMembers] = useState([]); + const [orgMembers, setOrgMembers] = useState([ + 'kituuu', + 'karthik', + 'kartik', + 'kartar', + ]); + const [searchedMembers, setSearchedMembers] = useState([]); + + const filterMembers = (search: string) => { + let temp: string[] = []; + orgMembers.forEach((member) => { + if (member.includes(search)) { + temp.push(member); + } + }); + + setSearchedMembers(temp); + }; + + const dataFetch = async () => { + try { + if (token && spaceName && projectName) { + const projectMemRes = await getMembers(token, projectName, spaceName); + const orgMemRes = await getOrgMembers(token, spaceName); + + setProjectMembers(Object.keys(projectMemRes.data.members)); + setOrgMembers(Object.keys(orgMemRes.data.members)); + } + } catch (e) {} + }; + + useEffect(() => { + dataFetch(); + }, [memberName]); + + const addMembers = () => { + if (memberName) { + if ( + orgMembers.includes(memberName) && + !members.includes(memberName) && + !projectMembers.includes(memberName) + ) { + setMembers([...members, memberName]); + setMemberName(null); + } else if (projectMembers.includes(memberName)) { + toast.error('Member already exists in the project'); + } + } + }; + + 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 (token) { + const func = async (): Promise => { + if (members.length > 0 && spaceName && projectName) { + try { + const addMmebersRes = await addProjectsMembers( + token, + projectName, + spaceName, + members + ); + } catch (e) {} + } + navigate(`/projectMembers/${spaceName}/${projectName}`); + }; + + toast.promise(func(), { + loading: 'On Progress', + success: Members added, + error: Could not add, + }); + } else { + toast.error('Invalid inputs'); + } + }; + + return ( +
+
+
+
+ ) => { + setMemberName(e.target.value); + if (memberName) filterMembers(memberName); + }} + placeholder='Github ID of user' + /> + +
+ {memberName && searchedMembers.length > 0 && ( +
+ {searchedMembers.map((member, index) => { + return ( +

{ + setMemberName(member); + setSearchedMembers([]); + }} + key={index} + > + {member} +

+ ); + })} +
+ )} +
+
+ {members.map((member, index) => { + return ( +
+ {member}

{' '} + +
+ ); + })} +
+ + +
+
+ ); +}; + +export default ProjectAddMember; diff --git a/src/features/WorkspaceAddMember/index.scss b/src/features/WorkspaceAddMember/index.scss new file mode 100644 index 0000000..f6f5ed5 --- /dev/null +++ b/src/features/WorkspaceAddMember/index.scss @@ -0,0 +1,162 @@ +$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..0126778 --- /dev/null +++ b/src/features/WorkspaceAddMember/index.tsx @@ -0,0 +1,171 @@ +import { getAllUser, getUser } from 'app/api/user'; +import { ChangeEvent, useContext, useEffect, useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import toast from 'react-hot-toast'; +import { + addOrgMembers, + getOrgMembers, +} from 'app/api/organization'; + + +import './index.scss'; +import UserContext from 'app/context/user/userContext'; +import { AVATAR_URL } from 'app/constants/api'; +import { AVATAR_API } from 'envConstants'; + +const WorkspaceAddMember = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + + const [members, setMembers] = useState([]); + const [memberName, setMemberName] = useState(null); + const [orgMembers, setOrgMembers] = useState([]); + const [users, setUsers] = useState([]); + const { spaceName } = useParams(); + + const dataFetch = async () => { + try { + if (token && spaceName) { + const users_aray: string[] = []; + + const allUser = await getAllUser(token); + const orgMembers = await getOrgMembers(token, spaceName); + allUser.data.users.forEach((user) => { + users_aray.push(user.username); + }); + setOrgMembers(Object.keys(orgMembers.data.members)); + + setUsers(users_aray); + } + } catch (e) {} + }; + + useEffect(() => { + dataFetch(); + }, []); + + const addMembers = () => { + if (memberName) { + if ( + users.includes(memberName) && + memberName != userContext?.username && + !members.includes(memberName) && + !orgMembers.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 (token) { + const func = async (): Promise => { + if (members.length > 0 && spaceName) { + try { + const addMmebersRes = await addOrgMembers( + token, + spaceName, + members + ); + } catch (e) {} + } + navigate(`/workspaceMembers/${spaceName}`); + }; + + toast.promise(func(), { + loading: 'On Progress', + success: Members added, + error: Could not add, + }); + } else { + toast.error('Invalid inputs'); + } + }; + + return ( +
+
+
+
+ ) => { + setMemberName(e.target.value); + }} + placeholder='Github ID of user' + /> + +
+
+
+ {members.map((member, index) => { + return ( +
+ {member}

{' '} + +
+ ); + })} +
+ + +
+
+ ); +}; + +export default WorkspaceAddMember; diff --git a/src/features/home/components/leaderboard/index.tsx b/src/features/home/components/leaderboard/index.tsx deleted file mode 100644 index ea38cd4..0000000 --- a/src/features/home/components/leaderboard/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from 'react'; -import gold from 'app/assets/images/gold.svg'; -import silver from 'app/assets/images/silver.svg'; -import bronze from 'app/assets/images/bronze.svg'; -import { mockData } from 'app/utils/data'; -import { sortJSON } from 'app/utils/sort'; -import leftNavButton from 'app/assets/images/left_navigation_button.svg'; -import rightNavButton from 'app/assets/images/right_navigation_button.svg'; -import mockdatatypes from 'app/models/mockDataTypes'; -import './index.scss'; - - - -const LeaderBoard = () => { - const [itemOffset, setItemOffset] = useState(0); - const [pageNumber, setPageNumber] = useState(1); - const newMockData = sortJSON(mockData); - - const [items] = useState(newMockData); - const itemsPerPage = 4; - const itemsLength = newMockData.length; - const pageCount = Math.ceil(itemsLength / itemsPerPage); - - const handlePageClick = (_pageNumber: number) => { - if (_pageNumber >= 1 && _pageNumber <= pageCount) { - const newOffset = ((_pageNumber - 1) * itemsPerPage) % itemsLength; - setItemOffset(newOffset); - setPageNumber(_pageNumber); - } - }; - - return ( -
-
-
-
-
Name
-
Work
-
- - {items - .slice(itemOffset, itemOffset + itemsPerPage) - .map((e: mockdatatypes) => { - if (e.Rank <= 3) { - return ( -
-
- top-rank-medal -
-
{e.Name}
-
{e.PR}
-
- ); - } else { - return ( -
-
{e.Rank}
-
{e.Name}
-
{e.PR}
-
- ); - } - })} -
-
- leftNavbutton handlePageClick(pageNumber - 1)} - /> - {pageNumber} - rightNavbutton handlePageClick(pageNumber + 1)} - /> -
-
-
-
- ); -}; - -export default LeaderBoard; diff --git a/src/features/home/components/projectCard/index.tsx b/src/features/home/components/projectCard/index.tsx deleted file mode 100644 index fefe3cb..0000000 --- a/src/features/home/components/projectCard/index.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useState } from 'react'; -import './index.scss'; -import axios from 'axios'; -import { getMembers, getProject } from 'app/api/project'; -import { useQuery } from 'react-query'; -import { GetProject } from 'app/api/project'; -import { ProjectMembers } from 'app/api/project'; -import { AVATAR_URL } from 'app/constants/api'; -import { AVATAR_API } from 'envConstants'; - interface Props{ - projectName: string, - orgName: string, - status: { - archeive: boolean, - bookmark: boolean, - }, - githubData:{ - pulls: number, - commits: number, - issues: number - } | null - -} - - - - - -const ProjectCard: React.FC = ({projectName,orgName ,status, githubData}) => { - const token= localStorage.getItem('token') - - const [ProjectData,SetProjectData]= useState(null) - const [projectMembers,setProjectMembers]= useState(null) - -const fetchProjectData= async()=>{ - if(token!=null){ - const project_data= await getProject(token,projectName, orgName); - SetProjectData(project_data.data) - return project_data.data - }else{ - return null - } -} - -const fetchProjectMembers=async()=>{ - if(token!=null){ - const members= await getMembers(token,projectName,orgName); - setProjectMembers(members.data) - return members.data - - } -} - - -const {data:project_data}= useQuery(`${projectName}${orgName}`,fetchProjectData) -const {data: project_members}= useQuery(`${projectName}${orgName}Members`, fetchProjectMembers); - - - - return ( -
-

{projectName}

-

- {project_data?project_data.description:<>} -

-
-
- Pull Requests - {githubData?githubData.pulls:<>} -
-
- Commits - {githubData?githubData.commits:<>} -
-
- Issues - {githubData?githubData.issues:<>} -
-
- -
    - {/*
  • A
  • -
  • B
  • -
  • C
  • -
  • D
  • */} - { - project_members&&Object.entries(project_members).slice(0,4).map(([key,value])=>{ - const url= AVATAR_URL+"/"+key+".png?apikey="+AVATAR_API - return
  • - }) - } -
-
- ); -}; - -export default ProjectCard; diff --git a/src/features/home/components/projectCardContainer/index.tsx b/src/features/home/components/projectCardContainer/index.tsx deleted file mode 100644 index 4e98788..0000000 --- a/src/features/home/components/projectCardContainer/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useState } from 'react'; -import ProjectCard from '../projectCard'; -import './index.scss'; -import { Projects } from 'app/api/organization'; -import { ProjectsGithubData } from 'app/api/githubData'; - -const arr = [1, 2, 3, 4, 5]; -interface Props{ - weekly: boolean, - orgName: string, - orgProjects: Projects | null, - monthlyOrgProjectsData:ProjectsGithubData | null, - weeklyOrgProjectsData: ProjectsGithubData | null -} - -const ProjectCardCont: React.FC = ({weekly,orgName ,orgProjects, monthlyOrgProjectsData, weeklyOrgProjectsData}) => { - - - const [archeive,setArcheive]= useState(false); - return ( - <> -
- -
-
- - - {orgProjects&&Object.entries(orgProjects).map(([key, value])=>{ - if(archeive&&value.archeive){ - if(weekly){ - let githubData=null - if(weeklyOrgProjectsData){ - githubData= weeklyOrgProjectsData[key] - } - - return ; - }else{ - let githubData=null - if(monthlyOrgProjectsData){ - githubData= monthlyOrgProjectsData[key] - } - return ; - - } - } - })} - - -
- - ); -}; - -export default ProjectCardCont; diff --git a/src/features/home/index.tsx b/src/features/home/index.tsx deleted file mode 100644 index ea2fac3..0000000 --- a/src/features/home/index.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import SearchBar from 'app/components/search'; -import TimeRangeSwitch from 'app/components/timeRangeSwitch'; -import ProjectCardCont from './components/projectCardContainer'; -import LeaderBoard from './components/leaderboard'; -import './index.scss'; -import { Navigate, useNavigate } from 'react-router-dom'; -import { getUser } from 'app/api/user'; -import toast from 'react-hot-toast'; -import { Query, useQuery } from 'react-query'; -import { getAllOrgs, getOrgProjects } from 'app/api/organization'; -import { - getOrgGithubData, - getOrgRank, - getProjectGithubData, -} from 'app/api/githubData'; -import { Projects } from 'app/api/organization'; -import { ProjectsGithubData } from 'app/api/githubData'; -import { Contributors } from 'app/api/githubData'; -import loader from '../../app/assets/gifs/loader.gif'; - -const Home = () => { - const navigate = useNavigate(); - const token = localStorage.getItem('token'); - const [userData, setUserData] = useState(null); - const [weekly,setWeekly]= useState(true); - const [orgProjects, setOrgProjects] = useState(null); - const [monthlyOrgRank, setMonthlyOrgRank] = useState( - null - ); - const [weeklyOrgRank, setWeeklyOrgRank] = useState(null); - const [monthlyOrgProjectsData, setMOnthyOrgProjectsData] = - useState(null); - const [weeklyOrgProjectsData, setWeeklyOrgProjectsData] = - useState(null); - const orgName = 'fordev'; - const checklogin = async () => { - if (token != null) { - try { - const userData = await getUser(token); - - setUserData(userData.data.message); - } catch (e) { - toast.error('Session expired'); - navigate('/login'); - } - } else { - toast.error('Not authorized'); - navigate('/login'); - } - }; - - useEffect(() => { - checklogin(); - }, []); - - const fetchOrgProjects = async () => { - if (token) { - const orgProjects = await getOrgProjects(token, orgName); - - setOrgProjects(orgProjects.data.projects); - - - return { - orgProjects: orgProjects.data.projects, - - }; - } - }; - - - const fetchWeeklyData= async()=>{ - if(token){ - const weeklyOrgRank = await getOrgRank(token, orgName, true); - const weeklyOrgProjectsData = await getOrgGithubData( - token, - orgName, - false - ); - setWeeklyOrgProjectsData(weeklyOrgProjectsData.data.projects); - setWeeklyOrgRank(weeklyOrgRank.data.contributors); - return { - weeklyOrgRank: weeklyOrgRank.data.contributors, - weeklyOrgProjectsData: weeklyOrgProjectsData.data.projects, - } - - } - } - - const fetchMonthlyData= async()=>{ - if(token){ - const monthlyOrgRank = await getOrgRank(token, orgName, true); - const monthlyOrgProjectsData = await getOrgGithubData( - token, - orgName, - true - ); - setMonthlyOrgRank(monthlyOrgRank.data.contributors); - setMOnthyOrgProjectsData(monthlyOrgProjectsData.data.projects); - return{ - monthlyOrgProjectsData: monthlyOrgProjectsData.data.projects, - monthlyOrgRank: monthlyOrgRank.data.contributors, - } - } - } - - const { isLoading } = useQuery({ - queryFn: () => fetchOrgProjects(), - queryKey: 'OrgProjects', - }); - - const {} = useQuery({ - queryFn: ()=> fetchWeeklyData(), - queryKey: "weeklyData" - }) - - const {}= useQuery({ - queryFn: ()=>fetchMonthlyData(), - queryKey: "monthlyData" - }) - - - return ( - <> -
- - -
- {isLoading?
- -
: - -
- - -
- } - - - ); -}; - -export default Home; diff --git a/src/features/login/index.scss b/src/features/login/index.scss index cc68ed4..b7666e5 100644 --- a/src/features/login/index.scss +++ b/src/features/login/index.scss @@ -1,68 +1,65 @@ .login_wrapper { - display: flex; - flex-direction: row; - height: 100vh; - position: absolute; - z-index: -2; - top: -6vh; + display: flex; + flex-direction: row; + height: 100vh; + position: absolute; + z-index: -2; + top: -6vh; } .hero_content { - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - row-gap: 5vh; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + row-gap: 5vh; } .hero_content h1 { - color: rgba(255, 255, 255, 0.80); - font-family: Montserrat; - font-size: 59.815px; - font-style: normal; - font-weight: 700; - line-height: normal; - text-align: center; - text-transform: uppercase; + color: rgba(255, 255, 255, 0.8); + font-family: Montserrat; + font-size: 59.815px; + font-style: normal; + font-weight: 700; + line-height: normal; + text-align: center; + text-transform: uppercase; } .hero_content h3 { - color: rgba(223, 223, 223, 0.80); - text-align: center; - font-family: Poppins; - font-size: 24px; - font-style: normal; - font-weight: 400; - line-height: normal; + color: rgba(223, 223, 223, 0.8); + text-align: center; + font-family: Poppins; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: normal; } .hero_content button { - display: flex; - width: 372px; - height: 81px; - padding: 20px 0px 20px 30px; - align-items: center; - gap: 20px; - flex-shrink: 0; - border-radius: 14px; - border: 0.8px solid var(--Button, #402AA4); - background: #402AA4; - color: #ADADFF; - font-family: Poppins; - font-size: 20px; - font-style: normal; - font-weight: 500; - line-height: normal; + display: flex; + width: 372px; + height: 81px; + padding: 20px 0px 20px 30px; + align-items: center; + gap: 20px; + flex-shrink: 0; + border-radius: 14px; + border: 0.8px solid var(--Button, #402aa4); + background: #402aa4; + color: #adadff; + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 500; + line-height: normal; } - .hero_image img { - height: 105vh; + height: 105vh; } - -.loader{ - - height: 50px; - width: 50px; -} \ No newline at end of file +.loader { + height: 50px; + width: 50px; +} diff --git a/src/features/login/index.tsx b/src/features/login/index.tsx index 417b83e..4d45ee5 100644 --- a/src/features/login/index.tsx +++ b/src/features/login/index.tsx @@ -1,11 +1,10 @@ -import { useEffect } from 'react'; + import { CLIENT_ID } from '../../envConstants'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { login } from 'app/api/login'; import { useQuery } from 'react-query'; -import {getUser } from 'app/api/user'; import toast from 'react-hot-toast'; -import loader from '../../app/assets/gifs/loader.gif' +import loader from '../../app/assets/gifs/loader.gif'; import heroImg from 'app/assets/images/login2.png'; import github from 'app/assets/images/github.png'; @@ -14,82 +13,56 @@ import './index.scss'; const Login = () => { const [searchParam] = useSearchParams(); const navigate = useNavigate(); - - - const token=localStorage.getItem('token') - const checklogin=async()=>{ - if(token!=null){ - try{ - const userData= await getUser(token); - navigate('/') - - }catch(e){ - localStorage.removeItem('token') - navigate('/login') - } - - } - } - - useEffect(() => { - checklogin(); - }, []); - - const loginFunc = async () => { if (searchParam.get('code') !== null) { - const code: string = searchParam.get('code')!; - const loginData = await login(code); - const token = loginData.data.token; - localStorage.setItem('token', token); - toast.success("Login success") - navigate('/'); + try { + const code: string = searchParam.get('code')!; + const loginData = await login(code); + const token = loginData.data.token; + localStorage.setItem('token', token); + toast.success('Login success'); + navigate('/'); + } catch (e) { + toast.error('Some error occured'); + navigate('/login'); + } } }; - const { isError } = useQuery({ - queryFn: () => loginFunc(), - queryKey: 'loginData', + const {} = useQuery('loginData', loginFunc, { + enabled: true, + staleTime: Infinity, }); - - if(isError){ - toast.error('Some error occured') - navigate("/login") - - } - - function loginWithGithub() { window.location.assign( 'https://github.com/login/oauth/authorize?client_id=' + CLIENT_ID ); } - if(searchParam.get('code')!=null){ - return Loading... + if (searchParam.get('code') != null) { + return Loading...; } return ( <> -
-
- +
+
+ +
+
+

ACTIVITY LEADERBOARD

+

+ Track your progress, healthy competition in organization others, and + unleash your GitHub potential" +

+ + +
-
-

ACTIVITY LEADERBOARD

-

- Track your progress, healthy competition in organization others, and - unleash your GitHub potential" -

- - -
-
); - } - +}; export default Login; diff --git a/src/features/project-members /components/BackNavigation.tsx b/src/features/project-members /components/BackNavigation.tsx new file mode 100644 index 0000000..3d0761f --- /dev/null +++ b/src/features/project-members /components/BackNavigation.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface Prop { + spaceName: string | undefined; +} +const BackNavigation: React.FC = ({ spaceName }) => { + const navigate = useNavigate(); + return ( +
+ +

+ Project {'->'} Manage +

+
+ ); +}; + +export default BackNavigation; diff --git a/src/features/project-members /components/MemberCard.tsx b/src/features/project-members /components/MemberCard.tsx new file mode 100644 index 0000000..88c24d7 --- /dev/null +++ b/src/features/project-members /components/MemberCard.tsx @@ -0,0 +1,128 @@ + +import { + changeProjectMembersStatus, + removeProjectMembers, +} from 'app/api/project'; +import UserContext from 'app/context/user/userContext'; +import { + ChangeEvent, + useContext, +} from 'react'; +import toast from 'react-hot-toast'; + +const MemberCard = ({ + image, + name, + role, + orgMembers, + setProjectMembers, + projectMembers, + projectName, + spaceName, +}: { + image: string; + name: string; + role: string; + spaceName: string; + projectName: string; + orgMembers: { [username: string]: string } | null; + projectMembers: { [username: string]: string } | null; + setProjectMembers: (projectMembers: { [username: string]: string }) => void; +}) => { + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + const handleRemove = async () => { + if (token && spaceName && projectName && projectMembers) { + const func = async () => { + const res = await removeProjectMembers(token, projectName, spaceName, [ + name, + ]); + delete projectMembers[name]; + setProjectMembers(projectMembers); + }; + toast.promise(func(), { + loading: 'Removing', + success: Successfully removed, + error: Unable to remove, + }); + } + }; + + const HandleRoleChange = async (event: ChangeEvent) => { + const new_role = event.target.value; + if ( + token && + spaceName && + orgMembers && + projectMembers && + new_role != role + ) { + const func = async () => { + const res = await changeProjectMembersStatus( + token, + projectName, + spaceName, + { [name]: new_role } + ); + projectMembers[name] = new_role; + setProjectMembers(projectMembers); + }; + toast.promise(func(), { + loading: 'Changing Role', + success: Role changed, + error: Unable to change, + }); + } + }; + + return ( +
+
+ image +

{name}

+
+
+
+ {orgMembers && + projectMembers && + userContext?.username && + (orgMembers[userContext?.username.toString()] == + ('admin' || 'manager') || + userContext.username != name) && + (orgMembers[userContext?.username.toString()] == + ('admin' || 'manager') || + projectMembers[userContext?.username.toString()] == 'admin') ? ( + + ) : ( +
+ {role.charAt(0).toUpperCase() + role.slice(1).toLowerCase()} +
+ )} +
+ {orgMembers && + projectMembers && + userContext?.username && + (orgMembers[userContext?.username.toString()] == + ('admin' || 'manager') || + userContext.username != name) && + (orgMembers[userContext?.username.toString()] == + ('admin' || 'manager') || + projectMembers[userContext?.username.toString()] == 'admin') && ( + + )} +
+
+ ); +}; + +export default MemberCard; diff --git a/src/features/project-members /components/Options.tsx b/src/features/project-members /components/Options.tsx new file mode 100644 index 0000000..ecf9975 --- /dev/null +++ b/src/features/project-members /components/Options.tsx @@ -0,0 +1,44 @@ +import UserContext from 'app/context/user/userContext'; +import { useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface Props { + spaceName: string; + projectName: string; + projectMembers: { [username: string]: string } | null; + orgMembers: { [username: string]: string } | null; +} + +const Options: React.FC = ({ + spaceName, + projectName, + projectMembers, + orgMembers, +}) => { + const navigate = useNavigate(); + const userContext = useContext(UserContext); + return ( +
+ {orgMembers && + projectMembers && + spaceName && + projectName && + userContext?.username && + (orgMembers[userContext?.username?.toString()] == + ('admin' || 'manager') || + projectMembers[userContext?.username.toString()] == 'admin') && ( + + )} + {/* */} +
+ ); +}; + +export default Options; diff --git a/src/features/project-members /index.scss b/src/features/project-members /index.scss new file mode 100644 index 0000000..5344489 --- /dev/null +++ b/src/features/project-members /index.scss @@ -0,0 +1,181 @@ +.members-page-container { + margin: 0rem 2rem; + margin-top: 2rem; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + + .back-title-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } +} + +.options { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.button { + outline: none; + border: none; + color: white; + font-size: 1.4rem; + padding: 1rem 2rem; + border-radius: 3rem; + background: #402aa4; +} + +.add-people { + font-size: 1.2rem; + padding: 0.6rem 1.4rem; +} + +.title { + color: rgba(237, 237, 237, 0.7); + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 1.6px; + text-transform: capitalize; + .arrow { + color: rgba(237, 237, 237, 0.7); + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -7.6px; + text-transform: capitalize; + } +} + +.member-view { + margin-top: 1rem; + width: 60%; +} + +.members-list { + background: transparent; + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 2rem; +} +.member-card { + border-radius: 20px; + border: 0.5px solid #406a80; + background: #191938; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem 2rem; + position: relative; + + .member-info { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 40%; + gap: 0.5rem; + img { + width: 80px; + height: 80px; + border-radius: 50%; + } + .member-name { + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 1.6px; + text-transform: capitalize; + } + } + + .member-actions { + width: 50%; + padding: 1rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + .member-remove-btn { + right: 1rem; + text-decoration: none; + background: transparent; + border: none; + outline: none; + color: #db1b24; + font-size: 1.2rem; + } + .select-overlay { + padding-right: 1rem; + background: #402aa4; + cursor: pointer; + } + select { + border: none; + outline: none; + color: white; + font-size: 1.2rem; + padding: 0.6rem 1.4rem; + background: #402aa4; + cursor: pointer; + } + option { + padding: 1rem; + margin: 2rem; + } + } +} + +@media only screen and (max-width: 1400px) { + .member-view { + width: 80%; + } +} + +@media only screen and (max-width: 1100px) { + .member-card { + flex-direction: column; + .member-info { + width: 100%; + } + .member-actions { + width: 100%; + } + } +} + +.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/project-members /index.tsx b/src/features/project-members /index.tsx new file mode 100644 index 0000000..5af9b6d --- /dev/null +++ b/src/features/project-members /index.tsx @@ -0,0 +1,71 @@ +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'; +import { getMembers } from 'app/api/project'; +const ProjectMembers = () => { + const { spaceName, projectName } = useParams(); + const token = localStorage.getItem('token'); + const [orgMembers, setOrgMembers] = useState<{ + [username: string]: string; + } | null>(null); + const [projectMembers, setProjectMembers] = useState<{ + [username: string]: string; + } | null>(null); + + const dataFetch = async () => { + try { + if (spaceName && token && projectName) { + const projectMemRes = await getMembers(token, projectName, spaceName); + setProjectMembers(projectMemRes.data.members); + const orgMemRes = await getOrgMembers(token, spaceName); + setOrgMembers(orgMemRes.data.members); + } + } catch (e) {} + }; + useEffect(() => { + dataFetch(); + }, [setOrgMembers, setProjectMembers]); + + return ( +
+ + {spaceName && projectName && ( +
+ +
+ {orgMembers && + projectMembers && + Object.entries(projectMembers).map(([key, value]) => { + return ( + { - const { Name } = props; + const { Name, PR, Commits, Issues } = props; + const url = AVATAR_URL + '/' + Name + '.png?apikey=' + AVATAR_API; return (
- profile + profile

{Name}

Pull Requests - 2 + {PR}
Commits - 2 + {Commits}
Issues - 2 + {Issues}
diff --git a/src/features/project/components/contributors/index.tsx b/src/features/project/components/contributors/index.tsx index 99a063e..17c8100 100644 --- a/src/features/project/components/contributors/index.tsx +++ b/src/features/project/components/contributors/index.tsx @@ -1,11 +1,10 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import './index.scss'; import ContributorCard from '../contributorCard'; import next_contributor from 'app/assets/images/next_contributor.svg'; import previous_contributor from 'app/assets/images/previous_contributor.svg'; -import { mockData } from 'app/utils/data'; -import mockdatatypes from 'app/models/mockDataTypes'; import ReactSimplyCarousel from 'react-simply-carousel'; +import { Contributors as contri } from 'app/api/githubData'; const btn_style = { alignSelf: 'center', @@ -13,8 +12,62 @@ const btn_style = { border: 'none', }; -const Contributors = () => { +interface Props { + weekly: boolean; + monthlyData: contri | null; + weeklyData: contri | null; +} + +const Contributors: React.FC = ({ weekly, monthlyData, weeklyData }) => { const [activeSlideIndex, setActiveSlideIndex] = useState(0); + const [monthlyDataArray, setMonthlyDataArray] = useState< + { + index: number; + username: string; + commits: number; + issues: number; + pulls: number; + }[] + >([]); + const [weeklyDataArray, setWeeklyDataArray] = useState< + { + index: number; + username: string; + commits: number; + issues: number; + pulls: number; + }[] + >([]); + + const initialProcess = () => { + if (monthlyData) { + const contributorsArray = Object.entries(monthlyData).map( + ([username, data], index) => ({ + index, + username, + ...data, + }) + ); + contributorsArray.sort((a, b) => b.pulls - a.pulls); + setMonthlyDataArray(contributorsArray); + } + if (weeklyData) { + const contributorsArray = Object.entries(weeklyData).map( + ([username, data], index) => ({ + index, + username, + ...data, + }) + ); + contributorsArray.sort((a, b) => b.pulls - a.pulls); + setWeeklyDataArray(contributorsArray); + } + }; + + useEffect(() => { + initialProcess(); + }, [weekly, monthlyData, weeklyData]); + return (

Contributors

@@ -62,24 +115,43 @@ const Contributors = () => { speed={400} easing='linear' > - {mockData.map((e: mockdatatypes) => { - return ( -
- -
- ); - })} + {weekly + ? weeklyDataArray.map((e) => { + return ( +
+ +
+ ); + }) + : monthlyDataArray.map((e) => { + return ( +
+ +
+ ); + })}
diff --git a/src/features/project/index.tsx b/src/features/project/index.tsx index ef0f46d..17d9e85 100644 --- a/src/features/project/index.tsx +++ b/src/features/project/index.tsx @@ -1,14 +1,60 @@ -import React from 'react'; +import { useEffect, useState } from 'react'; import Contributors from './components/contributors'; import ButtonBar from 'app/components/buttonBar'; import './index.scss'; +import { useParams } from 'react-router-dom'; +import { GetProject, getProject } from 'app/api/project'; +import { + Contributors as contri, + getProjectGithubData, +} from 'app/api/githubData'; const ProjectPage = () => { + const { spaceName, projectName } = useParams(); + const token = localStorage.getItem('token'); + const [project, setProject] = useState(null); + const [weeklyData, setWeeklyData] = useState(null); + const [monthlyData, setMonthlyData] = useState(null); + const [weekly, setWeekly] = useState(true); + const fetchData = async () => { + if (spaceName && projectName && token) { + const project = await getProject(token, projectName, spaceName); + setProject(project.data); + const weeklyData = await getProjectGithubData( + token, + spaceName, + projectName, + false + ); + const monthlyData = await getProjectGithubData( + token, + spaceName, + projectName, + true + ); + setWeeklyData(weeklyData.data.contributors); + setMonthlyData(monthlyData.data.contributors); + } + }; + + useEffect(() => { + fetchData(); + }, []); + return ( <>
- - + +
); diff --git a/src/features/workspace-members /components/BackNavigation.tsx b/src/features/workspace-members /components/BackNavigation.tsx new file mode 100644 index 0000000..15a19bf --- /dev/null +++ b/src/features/workspace-members /components/BackNavigation.tsx @@ -0,0 +1,39 @@ +import { useNavigate } from 'react-router-dom'; + +const BackNavigation = () => { + const navigate = useNavigate(); + return ( +
+ +

+ Organization {'->'} Manage +

+
+ ); +}; + +export default BackNavigation; diff --git a/src/features/workspace-members /components/MemberCard.tsx b/src/features/workspace-members /components/MemberCard.tsx new file mode 100644 index 0000000..d8b0ac4 --- /dev/null +++ b/src/features/workspace-members /components/MemberCard.tsx @@ -0,0 +1,108 @@ +import { changeOrgMembersStatus, removeOrgMembers } from 'app/api/organization'; +import UserContext from 'app/context/user/userContext'; +import { + ChangeEvent, + useContext, +} from 'react'; +import toast from 'react-hot-toast'; + +const MemberCard = ({ + image, + name, + role, + orgMembers, + setOrgMembers, + spaceName, +}: { + image: string; + name: string; + role: string; + spaceName: string; + orgMembers: { [username: string]: string } | null; + setOrgMembers: (orgMembers: { [username: string]: string }) => void; +}) => { + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + const handleRemove = async () => { + if (token && spaceName && orgMembers) { + const func = async () => { + const res = await removeOrgMembers(token, spaceName, [name]); + delete orgMembers[name]; + setOrgMembers(orgMembers); + }; + toast.promise(func(), { + loading: 'Removing', + success: Successfully removed, + error: Unable to remove, + }); + } + }; + + const HandleRoleChange = async (event: ChangeEvent) => { + const new_role = event.target.value; + if (token && spaceName && orgMembers && new_role != role) { + const func = async () => { + const res = await changeOrgMembersStatus(token, spaceName, { + [name]: new_role, + }); + orgMembers[name] = new_role; + setOrgMembers(orgMembers); + + toast.promise(func(), { + loading: 'Changing Role', + success: Role changed, + error: Unable to change, + }); + orgMembers[name] = new_role; + setOrgMembers(orgMembers); + }; + toast.promise(func(), { + loading: 'Changing Role', + success: Role changed, + error: Unable to change, + }); + } + }; + + return ( +
+
+ image +

{name}

+
+
+
+ {orgMembers && + userContext?.username && + userContext.username != name && + orgMembers[userContext?.username.toString()] == 'admin' ? ( + + ) : ( +
+ {role.charAt(0).toUpperCase() + role.slice(1).toLowerCase()} +
+ )} +
+ {orgMembers && + userContext?.username && + userContext.username != name && + orgMembers[userContext?.username.toString()] == 'admin' && ( + + )} +
+
+ ); +}; + +export default MemberCard; diff --git a/src/features/workspace-members /components/Options.tsx b/src/features/workspace-members /components/Options.tsx new file mode 100644 index 0000000..da8fed5 --- /dev/null +++ b/src/features/workspace-members /components/Options.tsx @@ -0,0 +1,22 @@ +import { useNavigate } from 'react-router-dom'; + +interface Props { + spaceName: string; +} + +const Options: React.FC = ({ spaceName }) => { + const navigate = useNavigate(); + return ( +
+ + {/* */} +
+ ); +}; + +export default Options; diff --git a/src/features/workspace-members /index.scss b/src/features/workspace-members /index.scss new file mode 100644 index 0000000..5344489 --- /dev/null +++ b/src/features/workspace-members /index.scss @@ -0,0 +1,181 @@ +.members-page-container { + margin: 0rem 2rem; + margin-top: 2rem; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + + .back-title-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } +} + +.options { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.button { + outline: none; + border: none; + color: white; + font-size: 1.4rem; + padding: 1rem 2rem; + border-radius: 3rem; + background: #402aa4; +} + +.add-people { + font-size: 1.2rem; + padding: 0.6rem 1.4rem; +} + +.title { + color: rgba(237, 237, 237, 0.7); + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 1.6px; + text-transform: capitalize; + .arrow { + color: rgba(237, 237, 237, 0.7); + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -7.6px; + text-transform: capitalize; + } +} + +.member-view { + margin-top: 1rem; + width: 60%; +} + +.members-list { + background: transparent; + margin-top: 2rem; + display: flex; + flex-direction: column; + gap: 2rem; +} +.member-card { + border-radius: 20px; + border: 0.5px solid #406a80; + background: #191938; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 1rem 2rem; + position: relative; + + .member-info { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 40%; + gap: 0.5rem; + img { + width: 80px; + height: 80px; + border-radius: 50%; + } + .member-name { + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 1.6px; + text-transform: capitalize; + } + } + + .member-actions { + width: 50%; + padding: 1rem; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + .member-remove-btn { + right: 1rem; + text-decoration: none; + background: transparent; + border: none; + outline: none; + color: #db1b24; + font-size: 1.2rem; + } + .select-overlay { + padding-right: 1rem; + background: #402aa4; + cursor: pointer; + } + select { + border: none; + outline: none; + color: white; + font-size: 1.2rem; + padding: 0.6rem 1.4rem; + background: #402aa4; + cursor: pointer; + } + option { + padding: 1rem; + margin: 2rem; + } + } +} + +@media only screen and (max-width: 1400px) { + .member-view { + width: 80%; + } +} + +@media only screen and (max-width: 1100px) { + .member-card { + flex-direction: column; + .member-info { + width: 100%; + } + .member-actions { + width: 100%; + } + } +} + +.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 new file mode 100644 index 0000000..1c83020 --- /dev/null +++ b/src/features/workspace-members /index.tsx @@ -0,0 +1,58 @@ +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 { 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 && ( +
+ +
+ {orgMembers && + Object.entries(orgMembers).map(([key, value]) => { + return ( + { + const userContext = useContext(UserContext); + const [isLoad, setIsLoad] = useState(false); + const [archeives, setArcheives] = useState(false); + const token = localStorage.getItem('token'); + const navigate = useNavigate(); + const fetchData = async () => { + if (token && userContext?.username) { + setIsLoad(true); + try { + const userOrgs = await getUserOrgs( + token, + userContext?.username.toString() + ); + userContext?.setUserOrgs(userOrgs.data); + + } catch (e) {} + + setIsLoad(false); + } + }; + const searchValue = useSelector((state: any) => state.searchKeyword.value); + + useEffect(() => { + fetchData(); + }, [ + userContext?.setUsername, + userContext?.username, + navigate, + userContext?.setUserOrgs, + searchValue, + ]); + + const LogoutHandler = async () => { + try { + localStorage.removeItem('token'); + toast.success('Logout successful!'); + navigate('/login'); + } catch (e) {} + }; return (
+
- + + + + +
+
- {workSpaceData.map((workspace) => { - return ( - - ); - })} + {isLoad ? ( + + ) : ( + userContext?.userOrgs && + Object.entries(userContext.userOrgs.userOrgs) + .filter(([key, value]) => { + if (key.toLowerCase().includes(searchValue.toLowerCase())) + return [key, value]; + }) + .map(([orgName, details]) => { + return ( + + ); + }) + )}
); diff --git a/src/features/workspace-view/workspace-card/index.scss b/src/features/workspace-view/workspace-card/index.scss index c826b6c..d65e41f 100644 --- a/src/features/workspace-view/workspace-card/index.scss +++ b/src/features/workspace-view/workspace-card/index.scss @@ -3,9 +3,10 @@ position: relative; border: 2px solid #402aa4; border-radius: 20px; - width: 454px; - height: 256px; - padding: 12px 32px; + max-width: 554px; + width: 100%; + height: 306px; + padding: 2rem 3rem; } .workspace-logo img { background: #402aa4; @@ -18,17 +19,27 @@ .workspace-card-utils { display: flex; } +.members-view-container { + display: flex; + flex-direction: row; + gap: 2rem; + align-items: center; +} .workspace-members { - padding: 10px 17px; + margin-left: 2rem; + display: flex; + flex-direction: column; + gap: 1rem; + width: 100%; +} +.workspace-description { + height: 100%; + font-size: 1.1rem; + height: 100%; } .workspace-title { - font-family: 'Poppins'; - font-style: normal; - font-weight: 600; - font-size: 20px; - line-height: 30px; - /* identical to box height */ - + font-weight: 900 !important; + font-size: 1.6rem; color: #ededed; } .workspace-card-body { @@ -36,10 +47,11 @@ .workspace-details-btn { display: flex; justify-content: end; - height: 30px; + height: 50px; } .workspace-details-btn img { - border-radius: 20px; + border-radius: 2rem; + font-size: 1rem; } .hide { @@ -61,9 +73,10 @@ position: absolute; width: 111px; - height: 110px; + height: fit-content !important; background: rgba(26, 26, 57, 0.5); + background: black; border: 1px solid #402aa4; backdrop-filter: blur(2px); /* Note: backdrop-filter has minimal browser support */ @@ -84,3 +97,7 @@ opacity: 1; } } + +.image-stack { + margin-left: 1rem; +} diff --git a/src/features/workspace-view/workspace-card/index.tsx b/src/features/workspace-view/workspace-card/index.tsx index 12ce276..c95457d 100644 --- a/src/features/workspace-view/workspace-card/index.tsx +++ b/src/features/workspace-view/workspace-card/index.tsx @@ -1,45 +1,271 @@ -import React, { useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import './index.scss'; import rightNavbtn from '../../../app/assets/images/right_navigation_button.svg'; -import workspaceCardProps from 'app/models/workSpaceCardTypes'; -const WorkspaceCard = (props: workspaceCardProps) => { - const { imgURL, title, description } = props; +import { deleteOrg, getOrg, getOrgMembers } from 'app/api/organization'; +import { deleteFile, getIcon, getIconName } from 'app/api/file'; +import UserContext from 'app/context/user/userContext'; +import toast from 'react-hot-toast'; +import { FaBookmark } from "react-icons/fa"; + + +import { + UserOrgDetails, + setOrgArcheiveStatus, + 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; + archeive: boolean; + bookmark: boolean; + archeives: boolean; +}; + +interface members { + [username: string]: string; +} +const WorkspaceCard = (props: workspaceCardProps) => { + const { workspaceName, role, archeive, bookmark, archeives } = props; + const [description, setDescription] = useState(null); const [showPopUp, setShowPopUp] = useState(false); + const token = localStorage.getItem('token'); + const [imageSrc, setImageSrc] = useState(null); + const [members, setMembers] = useState(null); + const [membersArray, setMembersArray] = useState<{ username: string }[]>([]); + + const userContext = useContext(UserContext); + const navigate = useNavigate(); + const workSpaceData = async () => { + if (token && workspaceName && !workspaceName.endsWith('-userspace')) { + try { + const workspace_data = await getOrg(token, workspaceName); + + setDescription(workspace_data.data.description); + } catch (e) {} + try { + const image_data = await getIcon(token, workspaceName); + const objectUrl = URL.createObjectURL(image_data.data); + setImageSrc(objectUrl); + } catch (e) {} + 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) {} + + try { + const iconRes = await getIconName(token, workspaceName); + const fileName = iconRes.data.message; + const deleteRes = await deleteFile(token, fileName); + } catch (e) {} + } + }; + + const HandleDelete = async () => { + if (!workspaceName.endsWith('-userspace') && token) { + const func = async () => { + const res = await deleteOrg(token, workspaceName); + const orgs = userContext?.userOrgs; + + if (orgs?.userOrgs.hasOwnProperty(workspaceName)) { + const obj: UserOrgDetails = orgs.userOrgs; + delete obj[workspaceName]; + userContext?.setUserOrgs({ + userOrgs: obj, + }); + } + }; + toast.promise(func(), { + loading: 'Deleting', + success: Successfully Deleted, + error: Error while deleting, + }); + } + }; + + const HandlePin = async () => { + if (!workspaceName.endsWith('-userspace') && token) { + const initBmk = bookmark; + const func = async () => { + let status: { [key: string]: boolean } = { + [workspaceName]: !bookmark, + }; + + const res = await setOrgBookmarkStatus(token, status); + const orgs = userContext?.userOrgs; + if (orgs?.userOrgs.hasOwnProperty(workspaceName)) { + orgs.userOrgs[workspaceName].bookmark = (!bookmark).toString(); + userContext?.setUserOrgs(orgs); + } + }; + if (initBmk) { + toast.promise(func(), { + loading: 'Unpinning', + success: Successfully unpinned, + error: Error while unpinning, + }); + } else { + toast.promise(func(), { + loading: 'Pinning', + success: Successfully pinned, + error: Error while pinning, + }); + } + } + }; + + const HandleArchive = async () => { + if (!workspaceName.endsWith('-userspace') && token) { + const initArc = archeive; + + const func = async () => { + const status: { [key: string]: boolean } = { + [workspaceName]: !archeive, + }; + + const res = await setOrgArcheiveStatus(token, status); + + const orgs = userContext?.userOrgs; + if (orgs?.userOrgs.hasOwnProperty(workspaceName)) { + orgs.userOrgs[workspaceName].archeive = (!archeive).toString(); + userContext?.setUserOrgs(orgs); + } + }; + if (!initArc) { + toast.promise(func(), { + loading: 'Archiving', + success: Successfully archived, + error: Error, + }); + } else { + toast.promise(func(), { + loading: 'Unarchiving', + success: Successfully unarchived, + error: Error, + }); + } + } + }; + useEffect(() => { + workSpaceData(); + }, [userContext?.setUserOrgs, userContext?.setUsername]); + return ( -
-
-
setShowPopUp(showPopUp ? false : true)} - > - -
-
-
Pin
-
archive
-
delete
-
-
-
- -
-
-
{title}
-
img
+ <> + {archeive == archeives && ( +
+ +
+ {bookmark&&} +
setShowPopUp(showPopUp ? false : true)} + > + {!workspaceName.endsWith('-userspace') && ( + + )} +
+
+
+ {bookmark ? 'UnPin' : 'Pin'} +
+
+ {archeive ? 'Unarchive' : 'archive'} +
+ {members && + userContext?.username && + members[userContext?.username.toString()] === 'admin' && ( +
+ delete +
+ )} + + {members && + userContext?.username && + members[userContext?.username.toString()] === 'admin' && ( +
navigate(`/editWorkspace/${workspaceName}`)} + > + edit +
+ )} +
+
+
+ +
+
+
+ {workspaceName.endsWith('-userspace') + ? "USER's WORKSPACE" + : workspaceName} +
+
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
+
+
+
+
+ {workspaceName.endsWith('-userspace') && + "User's private workspace"} + {description ? description.substring(0, 120) + '...' : <>} +
+
+ navigate(`/workspace/${workspaceName}`)} + alt='' + /> +
-
- {description.substring(0, 120) + '...'} -
-
- -
-
-
+ )} + ); }; diff --git a/src/features/home/components/leaderboard/index.scss b/src/features/workspace/components/leaderboard/index.scss similarity index 100% rename from src/features/home/components/leaderboard/index.scss rename to src/features/workspace/components/leaderboard/index.scss diff --git a/src/features/workspace/components/leaderboard/index.tsx b/src/features/workspace/components/leaderboard/index.tsx new file mode 100644 index 0000000..76de64c --- /dev/null +++ b/src/features/workspace/components/leaderboard/index.tsx @@ -0,0 +1,132 @@ +import React, { useEffect, useState } from 'react'; +import gold from 'app/assets/images/gold.svg'; +import silver from 'app/assets/images/silver.svg'; +import bronze from 'app/assets/images/bronze.svg'; +import leftNavButton from 'app/assets/images/left_navigation_button.svg'; +import rightNavButton from 'app/assets/images/right_navigation_button.svg'; +import './index.scss'; +import { Contributors } from 'app/api/githubData'; + +interface Prop { + weekly: boolean; + weeklyOrgRank: Contributors | null; + monthlyOrgRank: Contributors | null; +} + +const LeaderBoard: React.FC = ({ + weekly, + weeklyOrgRank, + monthlyOrgRank, +}) => { + const [itemOffset, setItemOffset] = useState(0); + const [pageNumber, setPageNumber] = useState(1); + + // const newMockData = sortJSON(mockData); + + const [items, setItems] = useState< + { + index: number; + name: string; + issues: number; + commits: number; + pulls: number; + }[] + >([]); + + const itemsPerPage = 4; + + const [itemsLength, setItemsLength] = useState(0); + const [pageCount, setPageCount] = useState(0); + // const pageCount = Math.ceil(itemsLength / itemsPerPage); + + const handlePageClick = (_pageNumber: number) => { + if (_pageNumber >= 1 && _pageNumber <= pageCount) { + const newOffset = ((_pageNumber - 1) * itemsPerPage) % itemsLength; + setItemOffset(newOffset); + setPageNumber(_pageNumber); + } + }; + + useEffect(() => { + if (weekly && weeklyOrgRank) { + const arrayOfContributors = Object.entries(weeklyOrgRank).map( + ([name, data], index) => ({ index, name, ...data }) + ); + + setItems(arrayOfContributors); + setItemsLength(Object.keys(weeklyOrgRank).length); + setPageCount(Math.ceil(Object.keys(weeklyOrgRank).length / itemsPerPage)); + } else if (!weekly && monthlyOrgRank) { + const arrayOfContributors = Object.entries(monthlyOrgRank).map( + ([name, data], index) => ({ index, name, ...data }) + ); + + setItems(arrayOfContributors); + setItemsLength(Object.keys(monthlyOrgRank).length); + setPageCount( + Math.ceil(Object.keys(monthlyOrgRank).length / itemsPerPage) + ); + } + }, [weekly, weeklyOrgRank, monthlyOrgRank]); + + return ( +
+
+
+
+
Name
+
PR
+
+ + {items && + items.slice(itemOffset, itemOffset + itemsPerPage).map((e) => { + if (e.index + 1 <= 3) { + return ( +
+
+ top-rank-medal +
+
{e.name}
+
{e.pulls}
+
+ ); + } else { + return ( +
+
{e.index + 1}
+
{e.name}
+
{e.pulls}
+
+ ); + } + })} +
+
+ leftNavbutton handlePageClick(pageNumber - 1)} + /> + {pageNumber} + rightNavbutton handlePageClick(pageNumber + 1)} + /> +
+
+
+
+ ); +}; + +export default LeaderBoard; diff --git a/src/features/home/components/projectCard/index.scss b/src/features/workspace/components/projectCard/index.scss similarity index 52% rename from src/features/home/components/projectCard/index.scss rename to src/features/workspace/components/projectCard/index.scss index a50a842..eb9be42 100644 --- a/src/features/home/components/projectCard/index.scss +++ b/src/features/workspace/components/projectCard/index.scss @@ -1,13 +1,19 @@ .projectcard { + position: relative; border-radius: 1em; background-color: var(--home-page-card-bg); + max-width: 554px; + width: 90%; + height: 100%; + min-height: 257px; padding: 1em; -} -.projectcard:hover { - cursor: pointer; } +// .projectcard:hover { +// cursor: pointer; +// } + .projectcard h1 { color: #ededed; margin-bottom: 0.2em; @@ -18,6 +24,10 @@ margin-bottom: 0.6em; } +.invisible-height { + height: 30px; +} + .projectcard-status { display: flex; justify-content: space-between; @@ -81,3 +91,78 @@ .projectcard-contributor li:nth-child(4n-3) { background: #e8aa0a; } + +.image-stack { + margin-left: 1rem; +} + +.project-image { + display: inline-block; + margin-left: -10px; + width: 28px; + height: 28px; + border: 1px solid white; + border-radius: 50%; +} + +.hide { + display: none; +} +.workspace-popup { + position: absolute; + right: 50px; + top: 50px; + box-sizing: border-box; + + /* Auto layout */ + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 18px 33px; + gap: 10px; + + position: absolute; + width: 111px; + height: 110px; + + background: rgba(26, 26, 57, 0.5); + border: 1px solid #402aa4; + backdrop-filter: blur(2px); + /* Note: backdrop-filter has minimal browser support */ + + border-radius: 12px; +} + +.workspace-popup-btn { + position: absolute; + right: 20px; + top: 20px; +} +.workspace-popup-btn img { + color: white; +} +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.workspace-details-btn { + display: flex; + justify-content: end; + height: 50px; +} +.workspace-details-btn img { + border-radius: 2rem; + font-size: 1rem; +} + +.pinDiv{ + height: 12px; + +} \ No newline at end of file diff --git a/src/features/workspace/components/projectCard/index.tsx b/src/features/workspace/components/projectCard/index.tsx new file mode 100644 index 0000000..8d28722 --- /dev/null +++ b/src/features/workspace/components/projectCard/index.tsx @@ -0,0 +1,220 @@ +import React, { useContext, useEffect, useState } from 'react'; +import './index.scss'; +import { deleteProject, getMembers, getProject } from 'app/api/project'; +import { FaBookmark } from "react-icons/fa"; +import { GetProject } from 'app/api/project'; +import { AVATAR_URL } from 'app/constants/api'; +import { AVATAR_API } from 'envConstants'; +import UserContext from 'app/context/user/userContext'; +import { setArcheiveStatus, setBookmarkStatus } from 'app/api/organization'; +import toast from 'react-hot-toast'; +import { useNavigate } from 'react-router-dom'; +import rightNavbtn from '../../../../app/assets/images/right_navigation_button.svg'; +interface Props { + projectName: string; + orgName: string; + status: { + archeive: boolean; + bookmark: boolean; + }; + githubData: { + pulls: number; + commits: number; + issues: number; + } | null; +} + +const ProjectCard: React.FC = ({ + projectName, + orgName, + + status, + githubData, +}) => { + const token = localStorage.getItem('token'); + const [showPopUp, setShowPopUp] = useState(false); + const userContext = useContext(UserContext); + const navigate = useNavigate(); + const [pin, setPin] = useState(status.bookmark); + const [archive, setArchive] = useState(status.archeive); + const [project, setProject] = useState(null); + const [projectMembers, setProjectMembers] = useState< + { key: string; value: string }[] + >([]); + + const fetchProjectData = async () => { + if (token != null) { + const project_data = await getProject(token, projectName, orgName); + setProject(project_data.data); + } + }; + + const fetchProjectMembers = async () => { + if (token != null) { + const members = await getMembers(token, projectName, orgName); + const myArray = Object.entries(members.data.members).map( + ([key, value]) => ({ key, value }) + ); + + setProjectMembers(myArray); + } + }; + + const PinHandler = async () => { + if (token && orgName) { + let initial = pin; + const func = async () => { + const res = await setBookmarkStatus(token, orgName, { + [projectName]: !pin, + }); + setPin(!pin); + }; + + if (initial) { + toast.promise(func(), { + loading: 'On progress', + success: Project Unpinned, + error: unable to unpin, + }); + } else { + toast.promise(func(), { + loading: 'On progress', + success: Project pinned, + error: unable to pin, + }); + } + } + }; + + const ArchiveHandler = async () => { + if (token && orgName) { + let initial = archive; + const func = async () => { + const res = await setArcheiveStatus(token, orgName, { + [projectName]: !archive, + }); + setArchive(!archive); + }; + + if (initial) { + toast.promise(func(), { + loading: 'On progress', + success: Project Unarchived, + error: unable to Unarchive, + }); + } else { + toast.promise(func(), { + loading: 'On progress', + success: Project archived, + error: Unable to arhive, + }); + } + } + }; + + const DeleteHandler = async () => { + if (token && orgName) { + const func = async () => { + const res = await deleteProject(token, projectName, orgName); + }; + toast.promise(func(), { + loading: 'On progress', + success: Successfully deleted, + error: Unable to delete, + }); + } + }; + useEffect(() => { + fetchProjectData(); + fetchProjectMembers(); + }, [userContext?.setUsername, userContext?.setUserOrgs]); + + return ( +
+ +
+ {pin&&} +
+

{projectName}

+

{project ? project.description : <>}

+ + {(userContext?.userOrgs?.userOrgs[orgName].role === 'admin' || + userContext?.userOrgs?.userOrgs[orgName].role === 'manager') && ( + <> + +
setShowPopUp(showPopUp ? false : true)} + > + +
+
+
+ {pin ? 'Unpin' : 'Pin'} +
+
+ {archive ? 'Unarchive' : 'Archive'} +
+
navigate(`/editProject/${orgName}/${projectName}`)} + > + Edit +
+
+ Delete +
+
+ + )} +
+
+ Pull Requests + {githubData ? githubData.pulls : <>} +
+
+ Commits + {githubData ? githubData.commits : <>} +
+
+ Issues + {githubData ? githubData.issues : <>} +
+
+ + {orgName && !orgName.endsWith('-userspace') && ( +
navigate(`/projectMembers/${orgName}/${projectName}`)} + > + {projectMembers && projectMembers.length > 0 ? ( + projectMembers.slice(0, 4).map((obj) => { + const url = + AVATAR_URL + '/' + obj.key + '.png?apikey=' + AVATAR_API; + return ; + }) + ) : ( + <> +
add Members
+ + )} +
+ )} + {orgName && !orgName.endsWith('-userspace') && ( +
+ navigate(`/project/${orgName}/${projectName}`)} + alt='' + /> +
+ )} +
+ ); +}; + +export default ProjectCard; diff --git a/src/features/home/components/projectCardContainer/index.scss b/src/features/workspace/components/projectCardContainer/index.scss similarity index 70% rename from src/features/home/components/projectCardContainer/index.scss rename to src/features/workspace/components/projectCardContainer/index.scss index a3d0b26..d392ece 100644 --- a/src/features/home/components/projectCardContainer/index.scss +++ b/src/features/workspace/components/projectCardContainer/index.scss @@ -1,4 +1,5 @@ .projectcard-cont { + width: 100%; display: grid; grid-template-columns: repeat(2, 1fr); row-gap: 2em; @@ -13,3 +14,9 @@ grid-template-columns: 1fr; } } + +@media only screen and (max-width: 860px) { + .projectcard-cont { + grid-template-columns: 1fr; + } +} diff --git a/src/features/workspace/components/projectCardContainer/index.tsx b/src/features/workspace/components/projectCardContainer/index.tsx new file mode 100644 index 0000000..bd64cd2 --- /dev/null +++ b/src/features/workspace/components/projectCardContainer/index.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from 'react'; +import ProjectCard from '../projectCard'; +import './index.scss'; +import { Projects } from 'app/api/organization'; +import { ProjectsGithubData } from 'app/api/githubData'; +import { useSelector } from 'react-redux'; + +interface Props { + weekly: boolean; + orgName: string; + orgProjects: Projects | null; + monthlyOrgProjectsData: ProjectsGithubData | null; + weeklyOrgProjectsData: ProjectsGithubData | null; + archives: boolean; +} + +const ProjectCardCont: React.FC = ({ + weekly, + orgName, + orgProjects, + monthlyOrgProjectsData, + weeklyOrgProjectsData, + archives, +}) => { + const searchValue = useSelector((state: any) => state.searchKeyword.value); + + useEffect(() => {}, [weekly, searchValue]); + + return ( + <> +
+ {orgProjects && + Object.entries(orgProjects) + .filter(([key, value]) => { + if (key.toLowerCase().includes(searchValue.toLowerCase())) + return [key, value]; + }) + .map(([key, value]) => { + return ( + archives === value.archeive && ( + + ) + ); + })} +
+ + ); +}; + +export default ProjectCardCont; diff --git a/src/features/home/index.scss b/src/features/workspace/index.scss similarity index 52% rename from src/features/home/index.scss rename to src/features/workspace/index.scss index 3fcdedb..899d134 100644 --- a/src/features/home/index.scss +++ b/src/features/workspace/index.scss @@ -2,6 +2,7 @@ display: flex; justify-content: space-between; align-items: center; + gap: 0.5rem; padding-left: 1.3em; margin-top: 0.8em; margin-bottom: 0.8em; @@ -19,21 +20,54 @@ flex-grow: 1; } -.loader{ +.loader { width: 10px; height: 10px; position: absolute; top: 50%; left: 50%; - } -.loader-container{ +.loader-container { width: 50px; height: 50px; position: relative; padding: 50px; } +.home-header button { + border: none; + outline: none; + margin-right: 1em; + padding: 0.7em; + padding-left: 1.5em; + padding-right: 1.5em; + border-radius: 2em; + color: white; + font-size: 0.9rem; + background: var(--timerange-switch-btn-bg); + +} + +.loader-container { + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.31); + z-index: 1; + width: 100vw; + height: 100vh; + padding: 0px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .loading { + margin: 0; + padding: 0; + height: 4rem; + width: 4rem; + } +} @media only screen and (max-width: 860px) { .home-header { @@ -54,5 +88,3 @@ width: 90%; } } - - diff --git a/src/features/workspace/index.tsx b/src/features/workspace/index.tsx new file mode 100644 index 0000000..40ae173 --- /dev/null +++ b/src/features/workspace/index.tsx @@ -0,0 +1,143 @@ +import React, { useContext, useEffect, useState } from 'react'; +import SearchBar from 'app/components/search'; +import TimeRangeSwitch from 'app/components/timeRangeSwitch'; +import ProjectCardCont from './components/projectCardContainer'; +import LeaderBoard from './components/leaderboard'; +import './index.scss'; +import { useNavigate, useParams } from 'react-router-dom'; +import { getOrgProjects } from 'app/api/organization'; +import { + getOrgGithubData, + getOrgRank, +} from 'app/api/githubData'; +import { Projects } from 'app/api/organization'; +import { ProjectsGithubData } from 'app/api/githubData'; +import { Contributors } from 'app/api/githubData'; +import loader from '../../app/assets/gifs/loader.gif'; +import UserContext from 'app/context/user/userContext'; + + +const Workspace = () => { + const navigate = useNavigate(); + const token = localStorage.getItem('token'); + const userContext = useContext(UserContext); + const [weekly, setWeekly] = useState(true); + const [orgProjects, setOrgProjects] = useState(null); + const [archives, setArcheives] = useState(false); + const [monthlyOrgRank, setMonthlyOrgRank] = useState( + null + ); + const [weeklyOrgRank, setWeeklyOrgRank] = useState(null); + const [monthlyOrgProjectsData, setMOnthyOrgProjectsData] = + useState(null); + const [weeklyOrgProjectsData, setWeeklyOrgProjectsData] = + useState(null); + const { spaceName } = useParams(); + const [isLoading, setIsLoading] = useState(true); + const fetchOrgProjects = async () => { + setIsLoading(true); + if (token && spaceName) { + try { + const orgProjects = await getOrgProjects(token, spaceName); + setOrgProjects(orgProjects.data.projects); + } catch (e) { + navigate('/'); + } + } + setIsLoading(false); + }; + + const fetchWeeklyData = async () => { + try { + if (token && spaceName) { + const weeklyOrgRank = await getOrgRank(token, spaceName, true); + const weeklyOrgProjectsData = await getOrgGithubData( + token, + spaceName, + false + ); + + setWeeklyOrgProjectsData(weeklyOrgProjectsData.data.projects); + setWeeklyOrgRank(weeklyOrgRank.data.contributors); + } + } catch (e) {} + }; + + const fetchMonthlyData = async () => { + try { + if (token && spaceName) { + const monthlyOrgRank = await getOrgRank(token, spaceName, true); + const monthlyOrgProjectsData = await getOrgGithubData( + token, + spaceName, + true + ); + setMonthlyOrgRank(monthlyOrgRank.data.contributors); + setMOnthyOrgProjectsData(monthlyOrgProjectsData.data.projects); + } + } catch (e) {} + }; + + useEffect(() => { + fetchOrgProjects(); + fetchWeeklyData(); + fetchMonthlyData(); + }, [weekly, userContext?.setUsername, userContext?.setUserOrgs]); + + return ( + <> +
+ + + + {spaceName && + (userContext?.userOrgs?.userOrgs[spaceName].role == 'admin' || + userContext?.userOrgs?.userOrgs[spaceName].role == 'manager') && ( + + )} + + +
+ {isLoading ? ( +
+ +
+ ) : ( +
+ {spaceName && ( + + )} + +
+ )} + + ); +}; + +export default Workspace; diff --git a/src/features/home/slices/projectSearchSlice.ts b/src/features/workspace/slices/projectSearchSlice.ts similarity index 84% rename from src/features/home/slices/projectSearchSlice.ts rename to src/features/workspace/slices/projectSearchSlice.ts index ff31d91..4bbee7b 100644 --- a/src/features/home/slices/projectSearchSlice.ts +++ b/src/features/workspace/slices/projectSearchSlice.ts @@ -7,7 +7,7 @@ export const searchSlice = createSlice({ value: '', }, reducers: { - searchAction: (state, action: PayloadAction) => { + searchAction: (state: any, action: PayloadAction) => { state.value = action.payload; }, }, diff --git a/src/index.tsx b/src/index.tsx index 062718b..f4dc382 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,23 +6,24 @@ import { BrowserRouter } from 'react-router-dom'; import { store } from 'app/state/store'; import { Provider } from 'react-redux'; import { QueryClient, QueryClientProvider } from 'react-query'; +import UserState from 'app/context/user/userState'; - -const queryClient= new QueryClient() +const queryClient = new QueryClient(); const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); - root.render( - - - - - + + + + + + + );