From f7611b1057fa6c6d78d75c5404791a1f4ff59ff2 Mon Sep 17 00:00:00 2001 From: Evan Feenstra Date: Tue, 23 Jan 2024 10:47:37 -0800 Subject: [PATCH 01/26] 0.1.1 --- README.md | 10 ++- deploy/staging/docker-compose.yml | 126 ++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 deploy/staging/docker-compose.yml diff --git a/README.md b/README.md index 3dfa2611..2243a3b5 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,15 @@ **npm run build** -# publish +### docker build -docker tag sphinx-tribes sphinxlightning/sphinx-tribes-frontend:0.1.0 +docker build -t sphinx-tribes-frontend . -docker push sphinxlightning/sphinx-tribes-frontend:0.1.0 +### docker publish + +docker tag sphinx-tribes sphinxlightning/sphinx-tribes-frontend:0.1.1 + +docker push sphinxlightning/sphinx-tribes-frontend:0.1.1 docker tag sphinx-tribes sphinxlightning/sphinx-tribes-frontend:latest diff --git a/deploy/staging/docker-compose.yml b/deploy/staging/docker-compose.yml new file mode 100644 index 00000000..47b09e49 --- /dev/null +++ b/deploy/staging/docker-compose.yml @@ -0,0 +1,126 @@ +version: "2" + +services: + reverse-proxy: + # The official v2 Traefik docker image + image: traefik:v2.2.1 + # Enables the web UI and tells Traefik to listen to docker + # command: --configFile=/home/ec2-user/sphinx-deploy/traefik.yaml + ports: + # The HTTP port + - 80:80 + # The Web UI (enabled by --api.insecure=true) + - 8080:8080 + # entrypoints + - 443:443 + - 8883:8883 + volumes: + # So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock + - /home/ec2-user/sphinx-deploy/traefik.yaml:/etc/traefik/traefik.yaml + - /home/ec2-user/letsencrypt:/letsencrypt + environment: + - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY + - AWS_REGION=$AWS_REGION + logging: + options: + max-size: 10m + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 + + auth: + image: sphinx-auth + depends_on: + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.auth.rule=Host(`auth-test.sphinx.chat`)" + - "traefik.http.services.auth.loadbalancer.server.port=9090" + - "traefik.http.routers.auth.tls=true" + - "traefik.http.routers.auth.tls.certresolver=myresolver" + - "traefik.http.routers.auth.entrypoints=websecure" + restart: on-failure + environment: + - JWT_KEY=$JWT_KEY + - CLIENT_KEYS=$CLIENT_KEYS + - OAUTH_TIMEOUT=$OAUTH_TIMEOUT + - HOST=auth-test.sphinx.chat + + tribes: + image: sphinx-tribes + depends_on: + - auth + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.tribes.rule=Host(`api.tribes-test.sphinx.chat`) || Host(`api.people-test.sphinx.chat`) || Host(`api.community-test.sphinx.chat`)" + - "traefik.http.services.tribes.loadbalancer.server.port=5002" + - "traefik.http.routers.tribes.tls=true" + - "traefik.http.routers.tribes.tls.certresolver=myresolver" + - "traefik.http.routers.tribes.entrypoints=websecure" + restart: on-failure + environment: + - DATABASE_URL=$DATABASE_URL + - PODCAST_INDEX_SECRET=$PODCAST_INDEX_SECRET + - PODCAST_INDEX_KEY=$PODCAST_INDEX_KEY + - YOUTUBE_KEY=$YOUTUBE_KEY + - GITHUB_TOKEN=$GITHUB_TOKEN + - LN_SERVER_BASE_URL=https://people-test.sphinx.chat + - LN_JWT_KEY=$LN_JWT_KEY + - RELAY_URL=$RELAY_URL + - RELAY_AUTH_KEY=$RELAY_AUTH_KEY + - RELAY_URL_BACKUP=$RELAY_URL_BACKUP + - RELAY_AUTH_KEY_BACKUP=$RELAY_AUTH_KEY_BACKUP + - HOST=people-test.sphinx.chat + - REDIS_URL=$REDIS_URL + - ADMIN_PUBKEYS=$ADMINS + - ADMINS=$ADMINS + - AWS_SECRET_ACCESS=$AWS_SECRET_ACCESS_KEY + - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + - AWS_REGION=us-east-1 + - S3_BUCKET_NAME=sphinx-tribes + - S3_FOLDER_NAME=metrics + - S3_URL=https://sphinx-tribes.s3.amazonaws.com + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 + + tribes-frontend: + image: stakwork/sphinx-tribes-frontend:latest + depends_on: + - tribes + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.tribes.rule=Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)" + - "traefik.http.services.tribes.loadbalancer.server.port=80" + - "traefik.http.routers.tribes.tls=true" + - "traefik.http.routers.tribes.tls.certresolver=myresolver" + - "traefik.http.routers.tribes.entrypoints=websecure" + restart: on-failure + + mqtt: + image: sphinx-mqtt + depends_on: + - auth + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.tcp.routers.mqtt.rule=HostSNI(`tribes-test.sphinx.chat`)" + - "traefik.tcp.services.mqtt.loadbalancer.server.port=1883" + - "traefik.tcp.routers.mqtt.tls=true" + - "traefik.tcp.routers.mqtt.tls.certresolver=myresolver" + - "traefik.tcp.routers.mqtt.entrypoints=mqttsecure" + - "traefik.tcp.routers.mqtt.service=mqtt" + restart: on-failure + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 From 07485db8e35ca5f97c313912fe87d30ecce22f3d Mon Sep 17 00:00:00 2001 From: Evan Feenstra Date: Tue, 23 Jan 2024 11:17:35 -0800 Subject: [PATCH 02/26] fix docker path --- deploy/staging/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/staging/docker-compose.yml b/deploy/staging/docker-compose.yml index 47b09e49..4f260b67 100644 --- a/deploy/staging/docker-compose.yml +++ b/deploy/staging/docker-compose.yml @@ -92,7 +92,7 @@ services: hard: 1000000 tribes-frontend: - image: stakwork/sphinx-tribes-frontend:latest + image: sphinxlightning/sphinx-tribes-frontend:latest depends_on: - tribes - reverse-proxy From 410acd65daf7341b13f1eb109de88353024d3e2b Mon Sep 17 00:00:00 2001 From: Evan Feenstra Date: Tue, 23 Jan 2024 12:17:07 -0800 Subject: [PATCH 03/26] fix docker compose file --- README.md | 6 +++--- deploy/staging/docker-compose.yml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2243a3b5..8c1d99cc 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ docker build -t sphinx-tribes-frontend . ### docker publish -docker tag sphinx-tribes sphinxlightning/sphinx-tribes-frontend:0.1.1 +docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.2 -docker push sphinxlightning/sphinx-tribes-frontend:0.1.1 +docker push sphinxlightning/sphinx-tribes-frontend:0.1.2 -docker tag sphinx-tribes sphinxlightning/sphinx-tribes-frontend:latest +docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:latest docker push sphinxlightning/sphinx-tribes-frontend:latest \ No newline at end of file diff --git a/deploy/staging/docker-compose.yml b/deploy/staging/docker-compose.yml index 4f260b67..11b89b9d 100644 --- a/deploy/staging/docker-compose.yml +++ b/deploy/staging/docker-compose.yml @@ -98,11 +98,11 @@ services: - reverse-proxy labels: - "traefik.enable=true" - - "traefik.http.routers.tribes.rule=Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)" - - "traefik.http.services.tribes.loadbalancer.server.port=80" - - "traefik.http.routers.tribes.tls=true" - - "traefik.http.routers.tribes.tls.certresolver=myresolver" - - "traefik.http.routers.tribes.entrypoints=websecure" + - "traefik.http.routers.tribes-frontend.rule=Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)" + - "traefik.http.services.tribes-frontend.loadbalancer.server.port=80" + - "traefik.http.routers.tribes-frontend.tls=true" + - "traefik.http.routers.tribes-frontend.tls.certresolver=myresolver" + - "traefik.http.routers.tribes-frontend.entrypoints=websecure" restart: on-failure mqtt: From b0df7337ae23b3230407f84c703af4c3ca411c6a Mon Sep 17 00:00:00 2001 From: Evan Feenstra Date: Tue, 23 Jan 2024 13:08:11 -0800 Subject: [PATCH 04/26] nginx config --- Dockerfile | 1 + README.md | 4 ++-- deploy/nginx.conf | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 deploy/nginx.conf diff --git a/Dockerfile b/Dockerfile index ef01f946..d8cb759b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,5 @@ COPY . . RUN yarn run build FROM nginx:alpine +COPY --from=build /usr/src/app/deploy/nginx.conf /etc/nginx/conf.d/default.conf COPY --from=build /usr/src/app/build /usr/share/nginx/html diff --git a/README.md b/README.md index 8c1d99cc..c749fc22 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ docker build -t sphinx-tribes-frontend . ### docker publish -docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.2 +docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.3 -docker push sphinxlightning/sphinx-tribes-frontend:0.1.2 +docker push sphinxlightning/sphinx-tribes-frontend:0.1.3 docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:latest diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 00000000..7bfc27a0 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,41 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + ## Your only path reference. + root /usr/share/nginx/html; + + ## This should be in your http block and if it is, it's not needed here. + index index.html; + + autoindex off; + + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /manifest.json { + allow all; + log_not_found off; + access_log off; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location / { + if (!-e $request_filename){ + rewrite ^(.*)$ /index.html break; + } + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { + expires max; + log_not_found off; + } +} \ No newline at end of file From 466428543ae7da8a4a5a09eb800986de1bb49748 Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 24 Jan 2024 14:30:54 +0100 Subject: [PATCH 05/26] added Github actions --- .github/ISSUE_TEMPLATE/bug_report.md | 33 +++++++++++++ .github/ISSUE_TEMPLATE/feature.md | 22 +++++++++ .github/pull_request_template.md | 19 +++++++ .github/workflows/docker-build.yml | 66 +++++++++++++++++++++++++ .github/workflows/prettierAndPackr2.yml | 33 +++++++++++++ .github/workflows/prjob_build.yml | 17 +++++++ .github/workflows/prjob_eslint.yml | 17 +++++++ .github/workflows/prjob_prettier.yml | 18 +++++++ .github/workflows/prjob_tests.yml | 16 ++++++ src/config/host.ts | 4 +- 10 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/prettierAndPackr2.yml create mode 100644 .github/workflows/prjob_build.yml create mode 100644 .github/workflows/prjob_eslint.yml create mode 100644 .github/workflows/prjob_prettier.yml create mode 100644 .github/workflows/prjob_tests.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..e572cc19 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bounties, bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. Include a screenshot or recording if you can. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Desktop (please complete the following information):** + - Browser [e.g. chrome, safari] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + +### Acceptance Criteria +- [ ] I have tested on Chrome desktop +- [ ] I have posted a screenshot or video if it is a change in the front end diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 00000000..566f8663 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,22 @@ +--- +name: Feature +about: Describe this issue template's purpose here. +title: '' +labels: bounties +assignees: '' + +--- + +### Context + +### Design + +### Acceptance Criteria +- [ ] I've tested on Chrome +- [ ] I've submitted a screenshot or recording in my pr +- [ ] I've created a unit test that + +Here is an [example unit test](https://github.com/stakwork/sphinx-tribes/blob/master/frontend/app/src/helpers/__test__/helpers.spec.ts) +- [ ] I've created a component test that + +Here is an [example component test](https://github.com/stakwork/sphinx-tribes/blob/9310f49b3b17a51992dada932f4298eb9eba15ff/frontend/app/src/people/widgetViews/__tests__/AboutView.spec.tsx) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..a46a6b18 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +## Describe your changes + +## Issue ticket number and link + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + + +## Checklist before requesting a review +- [ ] I have performed a self-review of my code +- [ ] I have tested on Chrome and Firefox +- [ ] I have tested on a mobile device +- [ ] I have provided a screenshot or recording of changes in my PR if there were updates to the frontend \ No newline at end of file diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000..342094c1 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,66 @@ +name: Docker build on push +env: + DOCKER_CLI_EXPERIMENTAL: enabled + +on: + push: + tags: + - '*' + +jobs: + build: + runs-on: ubuntu-20.04 + name: Build and push Tribes image + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + steps: + - name: Check out from Git + uses: actions/checkout@v2 + - name: Test env + run: echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + - name: Test print env + run: | + echo $RELEASE_TAG + echo ${{ env.RELEASE_TAG }} + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: Checkout project + uses: actions/checkout@v2 + - name: Setup Docker buildx action + uses: crazy-max/ghaction-docker-buildx@v1 + id: buildx + with: + buildx-version: latest + qemu-version: latest + - name: Show available buildx platforms + run: echo ${{ steps.buildx.outputs.platforms }} + - name: Cache Docker layers + uses: actions/cache@v2 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + - name: Test print env + run: | + echo $RELEASE_TAG + echo ${{ env.RELEASE_TAG }} + - name: Run Docker buildx + run: | + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache" \ + --platform linux/amd64,linux/arm64,linux/arm/v7 \ + --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-tribes:${{ env.RELEASE_TAG }}" \ + --output "type=registry" ./ + - name: Run Docker buildx + run: | + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache" \ + --platform linux/amd64,linux/arm64,linux/arm/v7 \ + --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-tribes:latest" \ + --output "type=registry" ./ + + diff --git a/.github/workflows/prettierAndPackr2.yml b/.github/workflows/prettierAndPackr2.yml new file mode 100644 index 00000000..b676c5b2 --- /dev/null +++ b/.github/workflows/prettierAndPackr2.yml @@ -0,0 +1,33 @@ +name: prettier and build and packr2 +on: + push: + branches: + - master + +jobs: + lint: + name: build + runs-on: + - ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.token }} + - uses: actions/setup-go@v3 + with: + go-version: '^1.18.3' + - run: go install github.com/gobuffalo/packr/v2/packr2 + - name: build and push automatic fixes + run: | + npm i --legacy-peer-deps && CI=false npm run build && cd .. && packr2 + npm run prettier + git config user.name 'Github Actions' + git config user.email github-actions@github.com + cd .. + git add ./packrd/packed-packr.go + cd app && git add . + git commit -m "[skip ci] Automatic build fixes" || echo -n + git push + + diff --git a/.github/workflows/prjob_build.yml b/.github/workflows/prjob_build.yml new file mode 100644 index 00000000..b60e41a2 --- /dev/null +++ b/.github/workflows/prjob_build.yml @@ -0,0 +1,17 @@ +name: Build +on: + pull_request: + branches: + - master +jobs: + build: + name: build + runs-on: + - ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn install + - name: Build + run: CI=false yarn run build + diff --git a/.github/workflows/prjob_eslint.yml b/.github/workflows/prjob_eslint.yml new file mode 100644 index 00000000..f352ab9b --- /dev/null +++ b/.github/workflows/prjob_eslint.yml @@ -0,0 +1,17 @@ +name: Eslint +on: + pull_request: + branches: + - master +jobs: + eslint: + name: eslint + runs-on: + - ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn install + - name: Eslint + run: yarn run lint + diff --git a/.github/workflows/prjob_prettier.yml b/.github/workflows/prjob_prettier.yml new file mode 100644 index 00000000..ec6403db --- /dev/null +++ b/.github/workflows/prjob_prettier.yml @@ -0,0 +1,18 @@ +name: Prettier +on: + pull_request: + branches: + - master +jobs: + + prettier: + name: prettier + runs-on: + - ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn install + - name: Prettier + run: CI=false yarn run prettier:check + diff --git a/.github/workflows/prjob_tests.yml b/.github/workflows/prjob_tests.yml new file mode 100644 index 00000000..55a57645 --- /dev/null +++ b/.github/workflows/prjob_tests.yml @@ -0,0 +1,16 @@ +name: Tests +on: + pull_request: + branches: + - master +jobs: + test-jest: + name: Jest + runs-on: + - ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install modules + run: yarn install + - name: Tests + run: NODE_OPTIONS="--max_old_space_size=8192" yarn run test-jest diff --git a/src/config/host.ts b/src/config/host.ts index 0a46e565..7804ecda 100644 --- a/src/config/host.ts +++ b/src/config/host.ts @@ -2,7 +2,9 @@ const internalDockerHosts = ['localhost:13007', 'localhost:13000']; const externalDockerHosts = ['localhost:23007', 'localhost:23000']; export function getHost(): string { - const host = window.location.host.includes('localhost') ? 'localhost:5002' : `api.${window.location.host}`; + const host = window.location.host.includes('localhost') + ? 'localhost:5002' + : `api.${window.location.host}`; return host; } From 4e24eb5e436a2c97a1751497c713fbb2e674fd94 Mon Sep 17 00:00:00 2001 From: MirzaHanan Date: Wed, 24 Jan 2024 19:20:17 +0500 Subject: [PATCH 06/26] fix(bounty-page): fixed bounty page go back to the home page --- src/pages/tickets/TicketModalPage.tsx | 18 +++++- .../__tests__/TicketModalPage.spec.tsx | 59 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/pages/tickets/__tests__/TicketModalPage.spec.tsx diff --git a/src/pages/tickets/TicketModalPage.tsx b/src/pages/tickets/TicketModalPage.tsx index 9ca485c5..a5919fbe 100644 --- a/src/pages/tickets/TicketModalPage.tsx +++ b/src/pages/tickets/TicketModalPage.tsx @@ -15,6 +15,7 @@ const focusedDesktopModalStyles = widgetConfigs.wanted.modalStyle; type Props = { setConnectPerson: (p: any) => void; + visible?: boolean; }; export const TicketModalPage = observer(({ setConnectPerson }: Props) => { @@ -28,7 +29,7 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { const [activeListIndex, setActiveListIndex] = useState(0); const [publicFocusIndex, setPublicFocusIndex] = useState(0); const [removeNextAndPrev, setRemoveNextAndPrev] = useState(false); - const { bountyId } = useParams<{ uuid: string; bountyId: string }>(); + const { uuid, bountyId } = useParams<{ uuid: string; bountyId: string }>(); const [activeBounty, setActiveBounty] = useState([]); const [visible, setVisible] = useState(false); const [isDeleted, setisDeleted] = useState(false); @@ -73,10 +74,21 @@ export const TicketModalPage = observer(({ setConnectPerson }: Props) => { getBounty(); }, [getBounty, removeNextAndPrev]); - const goBack = async () => { + const isDirectAccess = useCallback( + () => !document.referrer && location.pathname.includes('/bounty/'), + [location.pathname] + ); + + const goBack = () => { setVisible(false); setisDeleted(false); - history.goBack(); + + if (isDirectAccess()) { + const homePageUrl = uuid ? `/org/bounties/${uuid}` : '/bounties'; + history.push(homePageUrl); + } else { + history.goBack(); + } }; const directionHandler = (person: any, body: any) => { diff --git a/src/pages/tickets/__tests__/TicketModalPage.spec.tsx b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx new file mode 100644 index 00000000..e3a9e3c2 --- /dev/null +++ b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { useIsMobile } from 'hooks'; +import { useStores } from 'store'; +import { TicketModalPage } from '../TicketModalPage.tsx'; + +jest.mock('hooks', () => ({ + useIsMobile: jest.fn() +})); + +jest.mock('store', () => ({ + useStores: jest.fn() +})); + +const mockPush = jest.fn(); +const mockGoBack = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockPush, + goBack: mockGoBack + }), + useLocation: () => ({ + pathname: '/bounty/1239', + search: '', + state: {} + }), + useParams: () => ({ + uuid: 'ck95pe04nncjnaefo08g', + bountyId: '1239' + }) +})); + +describe('TicketModalPage Component', () => { + it('should redirect to the appropriate page on close based on the route and referrer', async () => { + // Mocking the useIsMobile hook + (useIsMobile as jest.Mock).mockReturnValue(false); + + (useStores as jest.Mock).mockReturnValue({ + main: { + getBountyById: jest.fn(), + getBountyIndexById: jest.fn() + } + }); + + render(); + + // eslint-disable-next-line @typescript-eslint/no-empty-function + await waitFor(() => {}); + + const closeButton = screen.queryByTestId('close-btn'); + if (closeButton) { + fireEvent.click(closeButton); + + expect(mockPush).toHaveBeenCalledWith('/bounties'); + } + }); +}); From 32a6e5b3a12d4b107c0355c8ce02858ef313cca8 Mon Sep 17 00:00:00 2001 From: MirzaHanan Date: Wed, 24 Jan 2024 20:27:40 +0500 Subject: [PATCH 07/26] fix(bounty-page): fixed prettier format --- src/pages/tickets/__tests__/TicketModalPage.spec.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/tickets/__tests__/TicketModalPage.spec.tsx b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx index e3a9e3c2..86f8bdc0 100644 --- a/src/pages/tickets/__tests__/TicketModalPage.spec.tsx +++ b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx @@ -34,7 +34,6 @@ jest.mock('react-router-dom', () => ({ describe('TicketModalPage Component', () => { it('should redirect to the appropriate page on close based on the route and referrer', async () => { - // Mocking the useIsMobile hook (useIsMobile as jest.Mock).mockReturnValue(false); (useStores as jest.Mock).mockReturnValue({ From 9e204853eebb4be1bff1a5e8513315a5a974505d Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 24 Jan 2024 18:57:37 +0100 Subject: [PATCH 08/26] bumped style components version --- .env | 1 - package.json | 6 +++--- src/people/widgetViews/summaries/WantedSummary.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 6bb7e398..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -EXTEND_ESLINT = true diff --git a/package.json b/package.json index 86e3a878..c0dd24c8 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "remark-gfm": "^3.0.1", "remark-rehype": "^10.1.0", "socket.io-client": "^4.6.1", - "styled-components": "^5.1.0", + "styled-components": "5.1.0", "yarn": "^1.22.19", "yup": "^0.32.9" }, @@ -186,7 +186,7 @@ "@types/react-dom": "18.0.4", "@types/react-router-dom": "^5.1.8", "@types/sinon": "^17.0.2", - "@types/styled-components": "^5.1.0", + "@types/styled-components": "5.1.26", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "bootstrap": "^4.5.0", @@ -203,4 +203,4 @@ "ts-loader": "^9.5.1", "typescript": "^5.3.2" } -} +} \ No newline at end of file diff --git a/src/people/widgetViews/summaries/WantedSummary.tsx b/src/people/widgetViews/summaries/WantedSummary.tsx index 4db5d9de..cb4a221f 100644 --- a/src/people/widgetViews/summaries/WantedSummary.tsx +++ b/src/people/widgetViews/summaries/WantedSummary.tsx @@ -498,7 +498,7 @@ function WantedSummary(props: WantedSummaryProps) { }} style={{ marginLeft: 3, fontWeight: 500, cursor: 'pointer' }} > - {assigneeInfo.owner_alias} + {/* {assigneeInfo.owner_alias} */} ); From 28ce73ca8fef7ae74334b1f5d37d811eeb6f3be2 Mon Sep 17 00:00:00 2001 From: Vividh Pandey Date: Wed, 24 Jan 2024 23:55:11 +0530 Subject: [PATCH 09/26] Added Readme Signed-off-by: Vividh Pandey --- CONTRIBUTING.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 53 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..97e7eee9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,57 @@ +# Frontend Contributing Guidelines 💻 + +Thank you for considering contributing to our project! Here are some guidelines to ensure smooth collaboration. + +### File Naming Convention 📁 + +- All React component files should be named in Pascal case (e.g., `MyComponent`). +- React component functions should also be named in Pascal case (e.g., `function MyComponent() {}`). +- Folders should be named in camel case (e.g., `peopleData`). +- Typescript files should follow camel case. +- Only the `index.tsx` files should be named in lowercase. + +### Prettier Fixing 🛠️ + +- Run the following command to fix Prettier errors before submitting: + ``` + yarn run prettier + ``` + +### Eslint Fixing 🚨 + +- Run the following command to fix ESLint issues and ensure all test cases pass: + ``` + yarn run eslint + ``` + +### Getting Started 🚀 + +1. Fork the repository. +2. Create a new branch for your contribution: + ``` + git checkout -b feature/your-feature + ``` +3. Make your changes. +4. Commit your changes: + ``` + git commit -m "Add your meaningful commit message" + ``` +5. Push to your fork: + ``` + git push origin feature/your-feature + ``` +6. Open a pull request to the `main` branch of the original repository. + +### Code Review ⚙️ + +All code contributions, including those with commit access, must go through a pull request and be approved by a core developer before being merged. This ensures a proper review of all the code. + +## About Sphinx Bounties 💬 + +A Unique bounty platform that rewards in Bitcoin. Join [Sphinx Chat](https://buy.sphinx.chat/), accomplish a bounty, and start earning Bitcoin! Explore our [website](https://people.sphinx.chat) for a list of available bounties. + +### Thank You ❤️ + +We appreciate your contribution to our project! Your efforts make a difference, and we look forward to collaborating with you. + +Feel free to connect with us on our [Sphinx Community](https://people.sphinx.chat/) and follow us on [Twitter](https://twitter.com/stakwork) for updates. diff --git a/README.md b/README.md index c749fc22..cc1e903f 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,52 @@ -# build +# Sphinx Tribes 🌐 -**npm run build** +![Tribes](https://github.com/stakwork/sphinx-tribes/raw/master/img/sphinx-tribes.png) -### docker build +Welcome to **sphinx-tribes** - the decentralized message broker designed for public groups in Sphinx. Empowering users to run their own **sphinx-tribes** server, this platform seamlessly routes group messages for various applications such as **sphinx-relay** nodes, apps, websites, or IoT devices. -docker build -t sphinx-tribes-frontend . +## How it Works 🚀 -### docker publish +**sphinx-tribes** operates as an MQTT broker that any node can subscribe to. Message topics always consist of two parts: `{receiverPubKey}/{groupUUID}`. Only the group owner has the privilege to publish messages, and all messages from group members are required to be submitted to the owner as a Lightning keysend payment. The group `uuid` is essentially a timestamp signed by the owner. -docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.3 +![Tribes](https://github.com/stakwork/sphinx-tribes/raw/master/img/tribes.jpg) -docker push sphinxlightning/sphinx-tribes-frontend:0.1.3 +## Authentication 🔒 -docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:latest +The authentication process is seamlessly handled by [sphinx-auth](https://github.com/stakwork/sphinx-auth). + +## Running Against Sphinx-Stack 🏃 + +To run the **tribes** frontend locally, utilize the following ports: + +- Tribes: `yarn start:tribes:docker` (localhost:23000) +- People: `yarn start:people:docker` (localhost:23007) + +## Running Frontend Against people.sphinx.chat Locally 🌐 + +If you wish to run only the frontend, follow these steps: + +1. Modify line 10 in `src/config/ModeDispatcher.tsx`: + - Change `'localhost:3000': AppMode.TRIBES` to `'localhost:3000': AppMode.COMMUNITY` + +2. Modify line 27 in `src/config/ModeDispatcher.tsx`: + - Change `return hosts[host] || AppMode.TRIBES;` to `return hosts[host] || AppMode.COMMUNITY;` + +3. Modify line 6 in `src/config/host.ts`: + - Change `return host;` to `return 'api.people-test.sphinx.chat';` + +4. Open the terminal. Locate your folder and then run: + +- `yarn install` to install the dependencies +- `yarn start` to run the frontend locally + +## Contributing Guidelines 🤝 + +All code contributions, including those of people having commit access, must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code. + +We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md). +## Community and Support 💬 + +Join our community on [Forum/Chat](link-to-community) to connect with other users and get support. + +Feel free to explore the potential of **sphinx-tribes** and contribute to its vibrant ecosystem! 🌟 -docker push sphinxlightning/sphinx-tribes-frontend:latest \ No newline at end of file From 8f7ab7a4fbc3dc22753b69f100032f4b7b08b3ce Mon Sep 17 00:00:00 2001 From: Vividh Pandey <91251535+VividhPandey003@users.noreply.github.com> Date: Wed, 24 Jan 2024 23:59:13 +0530 Subject: [PATCH 10/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc1e903f..0fc18e29 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ All code contributions, including those of people having commit access, must go We truly ❤️ pull requests! If you wish to help, you can learn more about how you can contribute to this project in the [contribution guide](CONTRIBUTING.md). ## Community and Support 💬 -Join our community on [Forum/Chat](link-to-community) to connect with other users and get support. +Join our community on [Forum/Chat](https://people.sphinx.chat) to connect with other users and get support. Feel free to explore the potential of **sphinx-tribes** and contribute to its vibrant ecosystem! 🌟 From 2b47e1c24143273316bb04effd32db733392d7cb Mon Sep 17 00:00:00 2001 From: Evan Feenstra Date: Wed, 24 Jan 2024 10:33:43 -0800 Subject: [PATCH 11/26] route frontend routes thru traefik --- README.md | 4 +- deploy/.gitignore | 3 +- deploy/prod/docker-compose.yml | 128 ++++++++++++++++++++++++++++++ deploy/prod/traefik.yaml | 30 +++++++ deploy/staging/docker-compose.yml | 6 +- src/config/host.ts | 2 +- 6 files changed, 167 insertions(+), 6 deletions(-) create mode 100644 deploy/prod/docker-compose.yml create mode 100644 deploy/prod/traefik.yaml diff --git a/README.md b/README.md index c749fc22..858d075c 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ docker build -t sphinx-tribes-frontend . ### docker publish -docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.3 +docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:0.1.4 -docker push sphinxlightning/sphinx-tribes-frontend:0.1.3 +docker push sphinxlightning/sphinx-tribes-frontend:0.1.4 docker tag sphinx-tribes-frontend sphinxlightning/sphinx-tribes-frontend:latest diff --git a/deploy/.gitignore b/deploy/.gitignore index 997f7152..2bc074a0 100644 --- a/deploy/.gitignore +++ b/deploy/.gitignore @@ -1 +1,2 @@ -updateTribes.sh \ No newline at end of file +updateTribes.sh +updateProdTribes.sh \ No newline at end of file diff --git a/deploy/prod/docker-compose.yml b/deploy/prod/docker-compose.yml new file mode 100644 index 00000000..72b9a657 --- /dev/null +++ b/deploy/prod/docker-compose.yml @@ -0,0 +1,128 @@ +version: '2' + +services: + reverse-proxy: + # The official v2 Traefik docker image + image: traefik:v2.2.1 + # Enables the web UI and tells Traefik to listen to docker + # command: --configFile=/home/ec2-user/sphinx-deploy/traefik.yaml + ports: + # The HTTP port + - 80:80 + # The Web UI (enabled by --api.insecure=true) + - 8080:8080 + # entrypoints + - 443:443 + - 8883:8883 + volumes: + # So that Traefik can listen to the Docker events + - /var/run/docker.sock:/var/run/docker.sock + - /home/ec2-user/sphinx-deploy/traefik.yaml:/etc/traefik/traefik.yaml + - /home/ec2-user/letsencrypt:/letsencrypt + environment: + - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY + - AWS_REGION=$AWS_REGION + logging: + options: + max-size: 10m + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 + + auth: + image: sphinxlightning/sphinx-auth:0.2.3 + depends_on: + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.auth.rule=Host(`auth.sphinx.chat`)" + - "traefik.http.services.auth.loadbalancer.server.port=9090" + - "traefik.http.routers.auth.tls=true" + - "traefik.http.routers.auth.tls.certresolver=myresolver" + - "traefik.http.routers.auth.entrypoints=websecure" + restart: on-failure + environment: + - JWT_KEY=$JWT_KEY + - CLIENT_KEYS=$CLIENT_KEYS + - OAUTH_TIMEOUT=$OAUTH_TIMEOUT + - HOST=auth.sphinx.chat + + tribes: + image: sphinx-tribes + depends_on: + - auth + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.tribes.rule=Host(`tribes.sphinx.chat`) || Host(`people.sphinx.chat`) || Host(`community.sphinx.chat`) || Host(`bounties.sphinx.chat`)" + - "traefik.http.routers.tribes.priority=1" + - "traefik.http.services.tribes.loadbalancer.server.port=5002" + - "traefik.http.routers.tribes.tls=true" + - "traefik.http.routers.tribes.tls.certresolver=myresolver" + - "traefik.http.routers.tribes.entrypoints=websecure" + restart: on-failure + environment: + - DATABASE_URL=$DATABASE_URL + - PODCAST_INDEX_SECRET=$PODCAST_INDEX_SECRET + - PODCAST_INDEX_KEY=$PODCAST_INDEX_KEY + - YOUTUBE_KEY=$YOUTUBE_KEY + - GITHUB_TOKEN=$GITHUB_TOKEN + - ALERT_SECRET=$ALERT_SECRET + - ALERT_TRIBE_UUID=$ALERT_TRIBE_UUID + - ALERT_BOT_ID=$ALERT_BOT_ID + - ALERT_URL=$ALERT_URL + - RELAY_URL=$RELAY_URL + - RELAY_AUTH_KEY=$RELAY_AUTH_KEY + - RELAY_URL_BACKUP=$RELAY_URL_BACKUP + - RELAY_AUTH_KEY_BACKUP= + - HOST=people.sphinx.chat + - STAKWORK_KEY=$STAKWORK_KEY + - AWS_SECRET_ACCESS=$AWS_SECRET_ACCESS_KEY + - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + - AWS_REGION=us-east-1 + - S3_BUCKET_NAME=sphinx-tribes + - S3_FOLDER_NAME=metrics + - S3_URL="https://sphinx-tribes.s3.amazonaws.com" + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 + + tribes-frontend: + image: sphinxlightning/sphinx-tribes-frontend:latest + depends_on: + - tribes + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.tribes-frontend.rule=(Host(`tribes.sphinx.chat`) || Host(`people.sphinx.chat`) || Host(`community.sphinx.chat`)|| Host(`bounties.sphinx.chat`)) && (Path(`/`) || PathPrefix(`/static`) || Path(`/manifest.json`) || Path(`/favicon.ico`) || Path(`/logo192.png`) || PathPrefix(`/t/`) || Path(`/t`) || PathPrefix(`/p/`) || Path(`/p`) || Path(`/tickets`) || Path(`/bounties`) || Path(`/bounty`) || PathPrefix(`/bounty/`) || Path(`/leaderboard`) || PathPrefix(`/org/`) || Path(`/admin`))" + - "traefik.http.routers.tribes-frontend.priority=2" + - "traefik.http.services.tribes-frontend.loadbalancer.server.port=80" + - "traefik.http.routers.tribes-frontend.tls=true" + - "traefik.http.routers.tribes-frontend.tls.certresolver=myresolver" + - "traefik.http.routers.tribes-frontend.entrypoints=websecure" + restart: on-failure + + mqtt: + image: sphinx-mqtt + depends_on: + - auth + - reverse-proxy + labels: + - "traefik.enable=true" + - "traefik.tcp.routers.mqtt.rule=HostSNI(`tribes.sphinx.chat`)" + - "traefik.tcp.services.mqtt.loadbalancer.server.port=1883" + - "traefik.tcp.routers.mqtt.tls=true" + - "traefik.tcp.routers.mqtt.tls.certresolver=myresolver" + - "traefik.tcp.routers.mqtt.entrypoints=mqttsecure" + - "traefik.tcp.routers.mqtt.service=mqtt" + restart: on-failure + ulimits: + nproc: 65535 + nofile: + soft: 1000000 + hard: 1000000 diff --git a/deploy/prod/traefik.yaml b/deploy/prod/traefik.yaml new file mode 100644 index 00000000..5f329af7 --- /dev/null +++ b/deploy/prod/traefik.yaml @@ -0,0 +1,30 @@ +api: + insecure: true + +providers: + docker: {} + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: "websecure" + scheme: "https" + permanent: true + + websecure: + address: ":443" + + mqttsecure: + address: ":8883" + +certificatesResolvers: + myresolver: + acme: + email: evanfeenstra@gmail.com + storage: /letsencrypt/acme.json + caServer: https://acme-v02.api.letsencrypt.org/directory + dnsChallenge: + provider: route53 diff --git a/deploy/staging/docker-compose.yml b/deploy/staging/docker-compose.yml index 11b89b9d..bc1a114b 100644 --- a/deploy/staging/docker-compose.yml +++ b/deploy/staging/docker-compose.yml @@ -57,7 +57,8 @@ services: - reverse-proxy labels: - "traefik.enable=true" - - "traefik.http.routers.tribes.rule=Host(`api.tribes-test.sphinx.chat`) || Host(`api.people-test.sphinx.chat`) || Host(`api.community-test.sphinx.chat`)" + - "traefik.http.routers.tribes.rule=Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)" + - "traefik.http.routers.tribes.priority=1" - "traefik.http.services.tribes.loadbalancer.server.port=5002" - "traefik.http.routers.tribes.tls=true" - "traefik.http.routers.tribes.tls.certresolver=myresolver" @@ -98,7 +99,8 @@ services: - reverse-proxy labels: - "traefik.enable=true" - - "traefik.http.routers.tribes-frontend.rule=Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)" + - "traefik.http.routers.tribes-frontend.rule=(Host(`tribes-test.sphinx.chat`) || Host(`people-test.sphinx.chat`) || Host(`community-test.sphinx.chat`)) && (Path(`/`) || PathPrefix(`/static`) || Path(`/manifest.json`) || Path(`/favicon.ico`) || Path(`/logo192.png`) || PathPrefix(`/t/`) || Path(`/t`) || PathPrefix(`/p/`) || Path(`/p`) || Path(`/tickets`) || Path(`/bounties`) || Path(`/bounty`) || PathPrefix(`/bounty/`) || Path(`/leaderboard`) || PathPrefix(`/org/`) || Path(`/admin`))" + - "traefik.http.routers.tribes-frontend.priority=2" - "traefik.http.services.tribes-frontend.loadbalancer.server.port=80" - "traefik.http.routers.tribes-frontend.tls=true" - "traefik.http.routers.tribes-frontend.tls.certresolver=myresolver" diff --git a/src/config/host.ts b/src/config/host.ts index 0a46e565..51a0f319 100644 --- a/src/config/host.ts +++ b/src/config/host.ts @@ -2,7 +2,7 @@ const internalDockerHosts = ['localhost:13007', 'localhost:13000']; const externalDockerHosts = ['localhost:23007', 'localhost:23000']; export function getHost(): string { - const host = window.location.host.includes('localhost') ? 'localhost:5002' : `api.${window.location.host}`; + const host = window.location.host.includes('localhost') ? 'localhost:5002' : window.location.host; return host; } From 0e335df6b7cbf73d2d4df0be269c0d34e646af05 Mon Sep 17 00:00:00 2001 From: ecurrencyhodler Date: Wed, 24 Jan 2024 11:58:13 -0800 Subject: [PATCH 12/26] Include labels in issue templates Include labels in issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- .github/ISSUE_TEMPLATE/feature.md | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e572cc19..3aea64ab 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: bounties, bug +labels: Bounties, bug assignees: '' --- @@ -30,4 +30,4 @@ A clear and concise description of what you expected to happen. ### Acceptance Criteria - [ ] I have tested on Chrome desktop -- [ ] I have posted a screenshot or video if it is a change in the front end +- [ ] I have posted a screenshot or video in my PR diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index 566f8663..fa479fab 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -2,7 +2,7 @@ name: Feature about: Describe this issue template's purpose here. title: '' -labels: bounties +labels: Bounties assignees: '' --- @@ -17,6 +17,5 @@ assignees: '' - [ ] I've created a unit test that Here is an [example unit test](https://github.com/stakwork/sphinx-tribes/blob/master/frontend/app/src/helpers/__test__/helpers.spec.ts) -- [ ] I've created a component test that Here is an [example component test](https://github.com/stakwork/sphinx-tribes/blob/9310f49b3b17a51992dada932f4298eb9eba15ff/frontend/app/src/people/widgetViews/__tests__/AboutView.spec.tsx) From e395c7d1755d9e1b7ad065dcc01511c757f05d4a Mon Sep 17 00:00:00 2001 From: Mirza Hanan <100023456+MirzaHanan@users.noreply.github.com> Date: Thu, 25 Jan 2024 00:58:56 +0500 Subject: [PATCH 13/26] Just trigger our github actions workflows for testing --- src/pages/tickets/__tests__/TicketModalPage.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tickets/__tests__/TicketModalPage.spec.tsx b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx index 86f8bdc0..0866e505 100644 --- a/src/pages/tickets/__tests__/TicketModalPage.spec.tsx +++ b/src/pages/tickets/__tests__/TicketModalPage.spec.tsx @@ -33,7 +33,7 @@ jest.mock('react-router-dom', () => ({ })); describe('TicketModalPage Component', () => { - it('should redirect to the appropriate page on close based on the route and referrer', async () => { + it('should redirect to the appropriate page on close based on the route', async () => { (useIsMobile as jest.Mock).mockReturnValue(false); (useStores as jest.Mock).mockReturnValue({ From 3772f41b763348b4a699af7a7dc89345061d5451 Mon Sep 17 00:00:00 2001 From: Yegor Litvyakov Date: Tue, 23 Jan 2024 20:39:53 +0300 Subject: [PATCH 14/26] fix(ui): keep `searchText` in local storage Fix: #1437 --- src/store/ui.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/store/ui.ts b/src/store/ui.ts index 9a3e16f6..7b9b4ee2 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -57,6 +57,7 @@ class UiStore { this.tags = t; } + @persist searchText = ''; setSearchText(s: string) { this.searchText = s.toLowerCase(); From e034d036f55559eaaa6da13bb297f28142a84356 Mon Sep 17 00:00:00 2001 From: Yegor Litvyakov Date: Tue, 23 Jan 2024 17:46:45 +0300 Subject: [PATCH 15/26] feat(people-search): add boundary hook for search The main purpose of the hook is to call appropriate methods of models Added for responsibility allocation and simplify testing of the functionality Relates: #1408 --- .../__tests__/usePeopleSearchHandler.spec.tsx | 22 +++++++++++++++++++ src/hooks/index.ts | 1 + src/hooks/usePeopleSearchHandler.ts | 11 ++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/hooks/__tests__/usePeopleSearchHandler.spec.tsx create mode 100644 src/hooks/usePeopleSearchHandler.ts diff --git a/src/hooks/__tests__/usePeopleSearchHandler.spec.tsx b/src/hooks/__tests__/usePeopleSearchHandler.spec.tsx new file mode 100644 index 00000000..298d60c2 --- /dev/null +++ b/src/hooks/__tests__/usePeopleSearchHandler.spec.tsx @@ -0,0 +1,22 @@ +import { mainStore } from 'store/main'; +import { uiStore } from 'store/ui'; +import { usePeopleSearchHandler } from 'hooks'; +import { act, renderHook } from '@testing-library/react-hooks'; + +jest.mock('store/ui'); +jest.mock('store/main'); + +describe('People Search Handler Hook', () => { + test('Handler should pass his argument to `ui.setSearchText` method and call `main.getPeople` method', async () => { + await act(async () => { + const { result } = renderHook(() => usePeopleSearchHandler()); + const handleSearchChange = result.current; + + handleSearchChange('Y'); + + expect(uiStore.setSearchText).toBeCalledWith('Y'); + + expect(mainStore.getPeople).toBeCalledTimes(1); + }); + }); +}); diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 92f36a0f..40110db8 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -3,3 +3,4 @@ export * from './useScroll'; export * from './uiHooks'; export * from './usePerson'; export * from './useInViewport'; +export * from './usePeopleSearchHandler'; diff --git a/src/hooks/usePeopleSearchHandler.ts b/src/hooks/usePeopleSearchHandler.ts new file mode 100644 index 00000000..96cc1523 --- /dev/null +++ b/src/hooks/usePeopleSearchHandler.ts @@ -0,0 +1,11 @@ +import { useStores } from 'store'; + +export function usePeopleSearchHandler() { + const { ui, main } = useStores(); + + return (newSearchText: string) => { + ui.setSearchText(newSearchText); + + main.getPeople({ page: 1, resetPage: true }); + }; +} From d197df17e11f9f679e181a9b39d42ba6cd3f1442 Mon Sep 17 00:00:00 2001 From: Yegor Litvyakov Date: Tue, 23 Jan 2024 17:57:24 +0300 Subject: [PATCH 16/26] refactor(people-list): rely on `usePeopleSearchHandler` hook Closes: #1408 --- src/pages/people/peopleList/PeopleList.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pages/people/peopleList/PeopleList.tsx b/src/pages/people/peopleList/PeopleList.tsx index eb2cf99a..4c67676e 100644 --- a/src/pages/people/peopleList/PeopleList.tsx +++ b/src/pages/people/peopleList/PeopleList.tsx @@ -1,5 +1,5 @@ import { Button, SearchTextInput } from 'components/common'; -import { usePageScroll } from 'hooks'; +import { usePageScroll, usePeopleSearchHandler } from 'hooks'; import NoResults from 'people/utils/NoResults'; import PageLoadSpinner from 'people/utils/PageLoadSpinner'; import React from 'react'; @@ -68,6 +68,7 @@ export const PeopleList = observer(() => { const { peoplePageNumber } = ui || {}; const history = useHistory(); const personId = ui.selectedPerson; + const handleSearchChange = usePeopleSearchHandler(); async function loadMorePeople(direction: number) { let newPage = peoplePageNumber + direction; @@ -113,9 +114,7 @@ export const PeopleList = observer(() => { border: '1px solid #DDE1E5', background: '#fff' }} - onChange={(e: any) => { - ui.setSearchText(e); - }} + onChange={handleSearchChange} /> From faced5635c8cf83d98d808c6d7ac2307893ca74a Mon Sep 17 00:00:00 2001 From: Vividh Pandey <91251535+VividhPandey003@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:10:59 +0530 Subject: [PATCH 17/26] updated backend api --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0fc18e29..84f28f9d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you wish to run only the frontend, follow these steps: - Change `return hosts[host] || AppMode.TRIBES;` to `return hosts[host] || AppMode.COMMUNITY;` 3. Modify line 6 in `src/config/host.ts`: - - Change `return host;` to `return 'api.people-test.sphinx.chat';` + - Change `return host;` to `return 'people-test.sphinx.chat';` 4. Open the terminal. Locate your folder and then run: From 60a1b811b3fd8db53c124ce5c07a8c4276928afa Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Thu, 25 Jan 2024 12:27:45 +0500 Subject: [PATCH 18/26] implement the status filter on the org homepage --- src/pages/tickets/org/OrgTickets.tsx | 19 +- .../org/orgHeader/Icons/checkboxImage.svg | 3 + src/pages/tickets/org/orgHeader/index.tsx | 229 ++++++++++++++++-- src/people/interfaces.ts | 6 + src/people/widgetViews/WidgetSwitchViewer.tsx | 5 +- .../widgetViews/__tests__/OrgHeader.spec.tsx | 65 ++++- src/store/main.ts | 86 +++++++ 7 files changed, 387 insertions(+), 26 deletions(-) create mode 100644 src/pages/tickets/org/orgHeader/Icons/checkboxImage.svg diff --git a/src/pages/tickets/org/OrgTickets.tsx b/src/pages/tickets/org/OrgTickets.tsx index 60a38272..0ac07099 100644 --- a/src/pages/tickets/org/OrgTickets.tsx +++ b/src/pages/tickets/org/OrgTickets.tsx @@ -10,6 +10,7 @@ import { colors } from '../../../config/colors'; import { useIsMobile } from '../../../hooks'; import { useStores } from '../../../store'; import { OrgBody, Body, Backdrop } from '../style'; +import { defaultOrgBountyStatus } from '../../../store/main'; import { OrgHeader } from './orgHeader'; function OrgBodyComponent() { @@ -18,8 +19,9 @@ function OrgBodyComponent() { const [showDropdown, setShowDropdown] = useState(false); const selectedWidget = 'wanted'; const [scrollValue, setScrollValue] = useState(false); - const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState({}); + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState(defaultOrgBountyStatus); const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({}); + const [languageString, setLanguageString] = useState(''); const { uuid } = useParams<{ uuid: string; bountyId: string }>(); const color = colors['light']; @@ -33,7 +35,7 @@ function OrgBodyComponent() { await main.getBadgeList(); await main.getPeople(); if (uuid) { - await main.getOrganizationBounties(uuid, { page: 1, resetPage: true }); + await main.getSpecificOrganizationBounties(uuid, { page: 1, resetPage: true }); } setLoading(false); })(); @@ -41,9 +43,10 @@ function OrgBodyComponent() { useEffect(() => { setCheckboxIdToSelectedMap({ - Open: true, + Open: false, Assigned: false, - Paid: false + Paid: false, + Completed: false }); }, [loading]); @@ -151,7 +154,12 @@ function OrgBodyComponent() { height: 'calc(100% - 65px)' }} > - + <>
diff --git a/src/pages/tickets/org/orgHeader/Icons/checkboxImage.svg b/src/pages/tickets/org/orgHeader/Icons/checkboxImage.svg new file mode 100644 index 00000000..6e48a429 --- /dev/null +++ b/src/pages/tickets/org/orgHeader/Icons/checkboxImage.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/tickets/org/orgHeader/index.tsx b/src/pages/tickets/org/orgHeader/index.tsx index c1cc2a82..dbecb5fa 100644 --- a/src/pages/tickets/org/orgHeader/index.tsx +++ b/src/pages/tickets/org/orgHeader/index.tsx @@ -1,9 +1,19 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; +import { EuiCheckboxGroup, EuiPopover, EuiText } from '@elastic/eui'; +import MaterialIcon from '@material/react-material-icon'; import { PostModal } from 'people/widgetViews/postBounty/PostModal'; +import { colors } from '../../../../config'; +import { OrgBountyHeaderProps } from '../../../../people/interfaces'; +import { useStores } from '../../../../store'; import addBounty from './Icons/addBounty.svg'; import searchIcon from './Icons/searchIcon.svg'; import file from './Icons/file.svg'; +import checkboxImage from './Icons/checkboxImage.svg'; + +interface styledProps { + color?: any; +} const Header = styled.div` width: 1366px; @@ -45,15 +55,7 @@ const FiltersRight = styled.span` flex: 1 0 0; width: 1366px; `; -const StatusContainer = styled.span` - padding: 10px 0px; - align-items: center; - gap: 4px; -`; -const Status = styled.select` - background-color: transparent; - border: none; -`; + const SkillContainer = styled.span` padding: 10px 0px; align-items: center; @@ -170,8 +172,140 @@ const Img = styled.img` padding-bottom: 10px; `; -export const OrgHeader = () => { +const EuiPopOverCheckbox = styled.div` + width: 147px; + height: auto; + padding: 15px 18px; + border-right: 1px solid ${(p: any) => p.color && p.color.grayish.G700}; + user-select: none; + .leftBoxHeading { + font-family: 'Barlow'; + font-style: normal; + font-weight: 700; + font-size: 12px; + line-height: 32px; + text-transform: uppercase; + color: ${(p: any) => p.color && p.color.grayish.G100}; + margin-bottom: 10px; + } + + &.CheckboxOuter > div { + display: flex; + flex-direction: column; + flex-wrap: wrap; + + .euiCheckboxGroup__item { + .euiCheckbox__square { + top: 5px; + border: 1px solid ${(p: any) => p?.color && p?.color?.grayish.G500}; + border-radius: 2px; + } + .euiCheckbox__input + .euiCheckbox__square { + background: ${(p: any) => p?.color && p?.color?.pureWhite} no-repeat center; + } + .euiCheckbox__input:checked + .euiCheckbox__square { + border: 1px solid ${(p: any) => p?.color && p?.color?.blue1}; + background: ${(p: any) => p?.color && p?.color?.blue1} no-repeat center; + background-size: contain; + background-image: url(${checkboxImage}); + } + .euiCheckbox__label { + font-family: 'Barlow'; + font-style: normal; + font-weight: 500; + font-size: 13px; + line-height: 16px; + color: ${(p: any) => p?.color && p?.color?.grayish.G50}; + &:hover { + color: ${(p: any) => p?.color && p?.color?.grayish.G05}; + } + } + input.euiCheckbox__input:checked ~ label { + color: ${(p: any) => p?.color && p?.color?.grayish.G05}; + } + } + } +`; + +const StatusContainer = styled.div` + width: 70px; + height: 48px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin-left: 19px; + margin-top: 4px; + cursor: pointer; + user-select: none; + .filterStatusIconContainer { + display: flex; + justify-content: center; + align-items: center; + height: 48px; + width: 38px; + .materialStatusIcon { + color: ${(p: any) => p.color && p.color.grayish.G200}; + cursor: pointer; + font-size: 18px; + margin-top: 5px; + } + } + .statusText { + font-family: 'Barlow'; + font-style: normal; + font-weight: 500; + font-size: 15px; + line-height: 17px; + letter-spacing: 0.15px; + display: flex; + align-items: center; + color: ${(p: any) => p.color && p.color.grayish.G200}; + } + &:hover { + .filterStatusIconContainer { + .materialStatusIcon { + color: ${(p: any) => p.color && p.color.grayish.G50} !important; + cursor: pointer; + font-size: 18px; + margin-top: 5px; + } + } + .statusText { + color: ${(p: any) => p.color && p.color.grayish.G50}; + } + } + &:active { + .filterStatusIconContainer { + .materialStatusIcon { + color: ${(p: any) => p.color && p.color.grayish.G10} !important; + cursor: pointer; + font-size: 18px; + margin-top: 5px; + } + } + .statusText { + color: ${(p: any) => p.color && p.color.grayish.G10}; + } + } +`; + +const Status = ['Open', 'Assigned', 'Completed', 'Paid']; +const color = colors['light']; +export const OrgHeader = ({ + onChangeStatus, + checkboxIdToSelectedMap, + org_uuid, + languageString +}: OrgBountyHeaderProps) => { + const { main } = useStores(); const [isPostBountyModalOpen, setIsPostBountyModalOpen] = useState(false); + const [isStatusPopoverOpen, setIsStatusPopoverOpen] = useState(false); + const onButtonClick = async () => { + setIsStatusPopoverOpen((isPopoverOpen: any) => !isPopoverOpen); + }; + const closeStatusPopover = () => setIsStatusPopoverOpen(false); + const selectedWidget = 'wanted'; const handlePostBountyClick = () => { setIsPostBountyModalOpen(true); @@ -180,6 +314,17 @@ export const OrgHeader = () => { setIsPostBountyModalOpen(false); }; + useEffect(() => { + if (org_uuid) { + main.getSpecificOrganizationBounties(org_uuid, { + page: 1, + resetPage: true, + ...checkboxIdToSelectedMap, + languageString + }); + } + }, [org_uuid, checkboxIdToSelectedMap]); + return ( <> @@ -193,10 +338,64 @@ export const OrgHeader = () => { - - - - + + + Status + +
+ +
+ + } + panelStyle={{ + border: 'none', + boxShadow: `0px 1px 20px ${color.black90}`, + background: `${color.pureWhite}`, + borderRadius: '0px 0px 6px 6px', + maxWidth: '140px', + minHeight: '160px', + marginTop: '0px', + marginLeft: '20px' + }} + isOpen={isStatusPopoverOpen} + closePopover={closeStatusPopover} + panelClassName="yourClassNameHere" + panelPaddingSize="none" + anchorPosition="downLeft" + > +
+ + ({ + label: `${status}`, + id: status + }))} + idToSelectedMap={checkboxIdToSelectedMap} + onChange={(id: any) => { + onChangeStatus(id); + }} + /> + +
+
diff --git a/src/people/interfaces.ts b/src/people/interfaces.ts index 1421ca90..91252fc7 100644 --- a/src/people/interfaces.ts +++ b/src/people/interfaces.ts @@ -418,6 +418,12 @@ export interface BountyHeaderProps { checkboxIdToSelectedMapLanguage: any; } +export interface OrgBountyHeaderProps { + onChangeStatus: (number) => void; + checkboxIdToSelectedMap?: any; + languageString?: string; + org_uuid?: string; +} export interface PeopleHeaderProps { onChangeLanguage: (number) => void; checkboxIdToSelectedMapLanguage: any; diff --git a/src/people/widgetViews/WidgetSwitchViewer.tsx b/src/people/widgetViews/WidgetSwitchViewer.tsx index a4e9f630..55393e1c 100644 --- a/src/people/widgetViews/WidgetSwitchViewer.tsx +++ b/src/people/widgetViews/WidgetSwitchViewer.tsx @@ -96,7 +96,7 @@ function WidgetSwitchViewer(props: any) { const { peoplePosts, peopleBounties, peopleOffers } = main; - const { selectedWidget, onPanelClick } = props; + const { selectedWidget, onPanelClick, org_uuid } = props; if (!selectedWidget) { return
; @@ -110,6 +110,9 @@ function WidgetSwitchViewer(props: any) { const activeList = [...listSource[selectedWidget]].filter(({ body }: any) => { const value = { ...body }; + if (org_uuid) { + return value.org_uuid === org_uuid; + } return value; }); diff --git a/src/people/widgetViews/__tests__/OrgHeader.spec.tsx b/src/people/widgetViews/__tests__/OrgHeader.spec.tsx index 57a15bf8..c7701ce0 100644 --- a/src/people/widgetViews/__tests__/OrgHeader.spec.tsx +++ b/src/people/widgetViews/__tests__/OrgHeader.spec.tsx @@ -1,26 +1,81 @@ import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { OrgHeader } from 'pages/tickets/org/orgHeader'; +import { OrgBountyHeaderProps } from '../../interfaces.ts'; +import { mainStore } from '../../../store/main.ts'; + +const MockProps: OrgBountyHeaderProps = { + checkboxIdToSelectedMap: { + Open: false, + Assigned: false, + Paid: false, + Completed: false + }, + languageString: '', + org_uuid: 'clf6qmo4nncmf23du7ng', + onChangeStatus: jest.fn() +}; describe('OrgHeader Component', () => { + beforeEach(() => { + jest.spyOn(mainStore, 'getSpecificOrganizationBounties').mockReset(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('renders the component correctly', () => { - render(); + render(); expect(screen.getByText('Post a Bounty')).toBeInTheDocument(); - expect(screen.getByLabelText('Status')).toBeInTheDocument(); + expect(screen.getByText('Status')).toBeInTheDocument(); expect(screen.getByLabelText('Skill')).toBeInTheDocument(); expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); expect(screen.getByText(/Bounties/i)).toBeInTheDocument(); }); it('opens the PostModal on "Post a Bounty" button click', async () => { - render(); + render(); fireEvent.click(screen.getByText('Post a Bounty')); }); it('displays the correct number of bounties', () => { - render(); + render(); expect(screen.getByText('284')).toBeInTheDocument(); expect(screen.getByText('Bounties')).toBeInTheDocument(); }); + + it('should trigger API call in response to click on status from OrgHeader', async () => { + const { getByText, getByRole, rerender } = render(); + + const statusFilter = getByText('Status'); + expect(statusFilter).toBeInTheDocument(); + fireEvent.click(statusFilter); + + const statusOpenCheckbox = getByRole('checkbox', { name: /Open/i }); + expect(statusOpenCheckbox).toBeInTheDocument(); + fireEvent.click(statusOpenCheckbox); + + await waitFor(() => { + expect(MockProps.onChangeStatus).toHaveBeenCalledWith('Open'); + + // Simulate a change in checkboxIdToSelectedMap + const updatedCheckboxIdToSelectedMap = { + ...MockProps.checkboxIdToSelectedMap, + Open: true + }; + + rerender( + + ); + + expect(mainStore.getSpecificOrganizationBounties).toHaveBeenCalledWith(MockProps.org_uuid, { + page: 1, + resetPage: true, + ...updatedCheckboxIdToSelectedMap, + languageString: MockProps.languageString + }); + }); + }); }); diff --git a/src/store/main.ts b/src/store/main.ts index 2c70e876..66c1f2ac 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -184,6 +184,7 @@ export interface QueryParams { search?: string; resetPage?: boolean; languages?: string; + org_uuid?: string; } export interface ClaimOnLiquid { @@ -291,6 +292,19 @@ export const defaultBountyStatus: BountyStatus = { Paid: false }; +export interface OrgBountyStatus { + Open: boolean; + Assigned: boolean; + Paid: boolean; + Completed: boolean; +} + +export const defaultOrgBountyStatus: OrgBountyStatus = { + Open: false, + Assigned: false, + Paid: false, + Completed: false +}; export class MainStore { [x: string]: any; tribes: Tribe[] = []; @@ -1074,6 +1088,78 @@ export class MainStore { } } + getWantedsSpecOrgPrevParams?: QueryParams = {}; + async getSpecificOrganizationBounties(uuid: string, params?: any): Promise { + const queryParams: QueryParams = { + limit: queryLimit, + sortBy: 'created', + search: uiStore.searchText ?? '', + page: 1, + resetPage: false, + ...params + }; + + if (params) { + // save previous params + this.getWantedsSpecOrgPrevParams = queryParams; + } + + // if we don't pass the params, we should use previous params for invalidate query + const query2 = this.appendQueryParams( + `organizations/bounties/${uuid}`, + queryLimit, + params ? queryParams : this.getWantedsOrgPrevParams + ); + try { + const ps2 = await api.get(query2); + const ps3: any[] = []; + + if (ps2 && ps2.length) { + for (let i = 0; i < ps2.length; i++) { + const bounty = { ...ps2[i].bounty }; + let assignee; + let organization; + const owner = { ...ps2[i].owner }; + + if (bounty.assignee) { + assignee = { ...ps2[i].assignee }; + } + + if (bounty.org_uuid) { + organization = { ...ps2[i].organization }; + } + + ps3.push({ + body: { ...bounty, assignee: assignee || '' }, + person: { ...owner, wanteds: [] } || { wanteds: [] }, + organization: { ...organization } + }); + } + } + + // for search always reset page + if (queryParams && queryParams.resetPage) { + this.setPeopleBounties(ps3); + uiStore.setPeopleBountiesPageNumber(1); + } else { + // all other cases, merge + const wanteds = this.doPageListMerger( + this.peopleBounties, + ps3, + (n: any) => uiStore.setPeopleBountiesPageNumber(n), + queryParams, + 'wanted' + ); + + this.setPeopleBounties(wanteds); + } + return ps3; + } catch (e) { + console.log('fetch failed getOrganizationBounties: ', e); + return []; + } + } + async getOrganizationBounties(uuid: string, queryParams?: any): Promise { queryParams = { ...queryParams, search: uiStore.searchText }; try { From 86947df9e9d6254a6f7ae770ec0a0ae344136310 Mon Sep 17 00:00:00 2001 From: aliraza556 Date: Thu, 25 Jan 2024 12:53:26 +0500 Subject: [PATCH 19/26] implement the organization website and github buttons on the org homepage --- src/pages/tickets/org/OrgTickets.tsx | 7 +- src/pages/tickets/org/orgHeader/index.tsx | 71 +++++++++++++++++-- src/people/interfaces.ts | 4 ++ .../widgetViews/__tests__/OrgHeader.spec.tsx | 29 +++++++- 4 files changed, 100 insertions(+), 11 deletions(-) diff --git a/src/pages/tickets/org/OrgTickets.tsx b/src/pages/tickets/org/OrgTickets.tsx index 60a38272..100b11da 100644 --- a/src/pages/tickets/org/OrgTickets.tsx +++ b/src/pages/tickets/org/OrgTickets.tsx @@ -10,6 +10,7 @@ import { colors } from '../../../config/colors'; import { useIsMobile } from '../../../hooks'; import { useStores } from '../../../store'; import { OrgBody, Body, Backdrop } from '../style'; +import api from '../../../api'; import { OrgHeader } from './orgHeader'; function OrgBodyComponent() { @@ -21,6 +22,7 @@ function OrgBodyComponent() { const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState({}); const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({}); const { uuid } = useParams<{ uuid: string; bountyId: string }>(); + const [organizationUrls, setOrganizationUrls] = useState({}); const color = colors['light']; @@ -34,6 +36,8 @@ function OrgBodyComponent() { await main.getPeople(); if (uuid) { await main.getOrganizationBounties(uuid, { page: 1, resetPage: true }); + const orgUrls = await api.get(`organizations/${uuid}`); + setOrganizationUrls(orgUrls); } setLoading(false); })(); @@ -151,7 +155,7 @@ function OrgBodyComponent() { height: 'calc(100% - 65px)' }} > - + <>
{ +export const OrgHeader = ({ organizationUrls }: OrgBountyHeaderProps) => { const [isPostBountyModalOpen, setIsPostBountyModalOpen] = useState(false); const selectedWidget = 'wanted'; + const { website, github } = organizationUrls; const handlePostBountyClick = () => { setIsPostBountyModalOpen(true); }; const handlePostBountyClose = () => { setIsPostBountyModalOpen(false); }; + const handleWebsiteButton = (websiteUrl: string) => { + window.open(websiteUrl, '_blank'); + }; + + const handleGithubButton = (githubUrl: string) => { + window.open(githubUrl, '_blank'); + }; return ( <>
+ + {website !== '' ? ( + handleWebsiteButton(website)}> + + Website + + ) : ( + '' + )} + {github !== '' ? ( + handleGithubButton(github)}> + + Github + + ) : ( + '' + )} +
- {main.createdBounties.map((w: any, i: any) => { - if (w.body.owner_id === person?.owner_pubkey) { - return ( - { - e.preventDefault(); - ui.setBountyPerson(person?.id); - history.push({ - pathname: `${url}/${w.body.id}/${i}` - }); - }} - > - - - ); - } - })} + {displayedBounties + .filter((w: BountyType) => w.body.owner_id === person?.owner_pubkey) + .map((w: BountyType, i: any) => ( + { + e.preventDefault(); + ui.setBountyPerson(person?.id); + history.push({ + pathname: `${url}/${w.body.id}/${i}`, + }); + }} + > + + + ))} + {hasMoreBounties && !loading &&( + +
+ Load More +
+
+ )} ); }); diff --git a/src/store/main.ts b/src/store/main.ts index 66c1f2ac..ee7ba3c2 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -11,6 +11,7 @@ import { uiStore } from './ui'; import { getUserAvatarPlaceholder } from './lib'; export const queryLimit = 10; +export const paginationQueryLimit = 20; export const peopleQueryLimit = 500; function makeTorSaveURL(host: string, key: string) { @@ -920,7 +921,7 @@ export class MainStore { async getPersonAssignedBounties(queryParams?: any, pubkey?: string): Promise { queryParams = { ...queryParams, search: uiStore.searchText }; - const query = this.appendQueryParams(`people/wanteds/assigned/${pubkey}`, queryLimit, { + const query = this.appendQueryParams(`people/wanteds/assigned/${pubkey}`, paginationQueryLimit, { sortBy: 'paid', ...queryParams }); @@ -967,7 +968,7 @@ export class MainStore { async getPersonCreatedBounties(queryParams?: any, pubkey?: string): Promise { queryParams = { ...queryParams, search: uiStore.searchText }; - const query = this.appendQueryParams(`people/wanteds/created/${pubkey}`, 20, { + const query = this.appendQueryParams(`people/wanteds/created/${pubkey}`, paginationQueryLimit, { ...queryParams, sortBy: 'paid' }); From fdb94c237b2fff4bdb85190a6ec998182d4f4c5b Mon Sep 17 00:00:00 2001 From: Vividh Pandey Date: Fri, 26 Jan 2024 15:37:01 +0530 Subject: [PATCH 25/26] test and prettier Signed-off-by: Vividh Pandey --- .../tabs/CreatedBountyLoadMore.spec.tsx | 43 +++++++++++++++++++ src/pages/people/tabs/Wanted.tsx | 15 ++++--- 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/pages/people/tabs/CreatedBountyLoadMore.spec.tsx diff --git a/src/pages/people/tabs/CreatedBountyLoadMore.spec.tsx b/src/pages/people/tabs/CreatedBountyLoadMore.spec.tsx new file mode 100644 index 00000000..d33a4617 --- /dev/null +++ b/src/pages/people/tabs/CreatedBountyLoadMore.spec.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import mockBounties from '../../../bounties/__mock__/mockBounties.data'; +import '@testing-library/jest-dom/extend-expect'; +import { Wanted } from './Wanted'; + +// eslint-disable-next-line @typescript-eslint/typedef +const createdMockBounties = Array.from({ length: 15 }, (_, index) => ({ + ...(mockBounties[0] || {}), + bounty: { + ...(mockBounties[0]?.bounty || {}), + id: mockBounties[0]?.bounty?.id + index + 1 + } +})); + +console.log(createdMockBounties); + +jest.mock('../../../bounties/__mock__/mockBounties.data', () => ({ + createdMockBounties +})); + +describe('Wanted component', () => { + it('displays "Load More" button when scrolling down', async () => { + const { getByText } = render( + + + + ); + + fireEvent.scroll(window, { target: { scrollY: 1000 } }); + + // Wait for the component to re-render (assuming there's an API call) + await waitFor(() => { + // Check if the "Load More" button is displayed + if (createdMockBounties.length > 20) { + expect(getByText('Load More')).toBeInTheDocument(); + } else { + console.warn('Not enough bounties for "Load More" button.'); + } + }); + }); +}); diff --git a/src/pages/people/tabs/Wanted.tsx b/src/pages/people/tabs/Wanted.tsx index 322c8d88..f2335190 100644 --- a/src/pages/people/tabs/Wanted.tsx +++ b/src/pages/people/tabs/Wanted.tsx @@ -9,7 +9,7 @@ import PageLoadSpinner from 'people/utils/PageLoadSpinner'; import React, { useEffect, useState } from 'react'; import { Route, Switch, useHistory, useRouteMatch, useParams } from 'react-router-dom'; import { useStores } from 'store'; -import { paginationQueryLimit } from 'store/main'; +import { paginationQueryLimit } from 'store/main'; import styled from 'styled-components'; import { LoadMoreContainer } from '../../../people/widgetViews/WidgetSwitchViewer'; import { colors } from '../../../config/colors'; @@ -57,7 +57,10 @@ export const Wanted = observer(() => { async function getUserTickets() { setIsLoading(true); // Fetch bounties for the specified page and limit - const response = await main.getPersonCreatedBounties({ page: page, limit: paginationQueryLimit }, personPubkey); + const response = await main.getPersonCreatedBounties( + { page: page, limit: paginationQueryLimit }, + personPubkey + ); // Check if the response has fewer bounties than the limit, indicating no more bounties to load if (response.length < paginationQueryLimit) { setHasMoreBounties(false); @@ -72,7 +75,7 @@ export const Wanted = observer(() => { setPage(nextPage); // Fetch bounties for the next page const response = await main.getPersonCreatedBounties( - { page: nextPage, limit: paginationQueryLimit }, + { page: nextPage, limit: paginationQueryLimit }, personPubkey ); // Check if the response has fewer bounties than the limit, indicating no more bounties to load @@ -146,14 +149,14 @@ export const Wanted = observer(() => { e.preventDefault(); ui.setBountyPerson(person?.id); history.push({ - pathname: `${url}/${w.body.id}/${i}`, + pathname: `${url}/${w.body.id}/${i}` }); }} > ))} - {hasMoreBounties && !loading &&( + {hasMoreBounties && !loading && ( { display: 'flex', justifyContent: 'center', alignItems: 'center', - margin: '20px 0', + margin: '20px 0' }} >
From 62aef34f7bdd2a7ff98c157409bda883417b0181 Mon Sep 17 00:00:00 2001 From: Vividh Pandey Date: Fri, 26 Jan 2024 15:43:14 +0530 Subject: [PATCH 26/26] prettier fix Signed-off-by: Vividh Pandey --- src/store/main.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/store/main.ts b/src/store/main.ts index ee7ba3c2..c3e18c01 100644 --- a/src/store/main.ts +++ b/src/store/main.ts @@ -921,10 +921,14 @@ export class MainStore { async getPersonAssignedBounties(queryParams?: any, pubkey?: string): Promise { queryParams = { ...queryParams, search: uiStore.searchText }; - const query = this.appendQueryParams(`people/wanteds/assigned/${pubkey}`, paginationQueryLimit, { - sortBy: 'paid', - ...queryParams - }); + const query = this.appendQueryParams( + `people/wanteds/assigned/${pubkey}`, + paginationQueryLimit, + { + sortBy: 'paid', + ...queryParams + } + ); try { const ps2 = await api.get(query);