diff --git a/README.md b/README.md index 13fc9abd..3206baa7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,47 @@ Additionally, a subset of **Sphinx Tribes** is the Bounties Platform - a global The **sphinx-tribes-frontend** also contains the frontend for the bounties platform website. In order to run the website locally on your computer, you'll need to run sphinx-tribes-frontend as a whole. You can find the backend repo at [sphinx-tribes](https://github.com/stakwork/sphinx-tribes). +## Authentication 🔒 + +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 '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](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! 🌟 +======= + ## Authentication 🔒 The authentication process is seamlessly handled by [sphinx-auth](https://github.com/stakwork/sphinx-auth). diff --git a/src/pages/tickets/org/OrgTickets.tsx b/src/pages/tickets/org/OrgTickets.tsx index 736a08ee..a424949c 100644 --- a/src/pages/tickets/org/OrgTickets.tsx +++ b/src/pages/tickets/org/OrgTickets.tsx @@ -22,8 +22,8 @@ function OrgBodyComponent() { const [scrollValue, setScrollValue] = useState(false); const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState(defaultOrgBountyStatus); const [checkboxIdToSelectedMapLanguage, setCheckboxIdToSelectedMapLanguage] = useState({}); - const [languageString, setLanguageString] = useState(''); const { uuid } = useParams<{ uuid: string; bountyId: string }>(); + const [languageString, setLanguageString] = useState(''); const [organizationUrls, setOrganizationUrls] = useState({}); const color = colors['light']; @@ -37,13 +37,17 @@ function OrgBodyComponent() { await main.getBadgeList(); await main.getPeople(); if (uuid) { - await main.getSpecificOrganizationBounties(uuid, { page: 1, resetPage: true }); + await main.getSpecificOrganizationBounties(uuid, { + page: 1, + resetPage: true, + languages: languageString + }); const orgUrls = await api.get(`organizations/${uuid}`); setOrganizationUrls(orgUrls); } setLoading(false); })(); - }, [main, uuid]); + }, [main, uuid, checkboxIdToSelectedMap, languageString]); useEffect(() => { setCheckboxIdToSelectedMap({ @@ -70,14 +74,17 @@ function OrgBodyComponent() { setCheckboxIdToSelectedMap(newCheckboxIdToSelectedMap); }; - const onChangeLanguage = (optionId: any) => { + const onChangeLanguage = (optionId: number) => { const newCheckboxIdToSelectedMapLanguage = { ...checkboxIdToSelectedMapLanguage, - ...{ - [optionId]: !checkboxIdToSelectedMapLanguage[optionId] - } + [optionId]: !checkboxIdToSelectedMapLanguage[optionId] }; setCheckboxIdToSelectedMapLanguage(newCheckboxIdToSelectedMapLanguage); + const languageString = Object.keys(newCheckboxIdToSelectedMapLanguage) + .filter((key: string) => newCheckboxIdToSelectedMapLanguage[key]) + .join(','); + setLanguageString(languageString); + main.setBountyLanguages(languageString); }; const onPanelClick = (person: any, item: any) => { @@ -160,11 +167,13 @@ function OrgBodyComponent() { }} > <>
diff --git a/src/pages/tickets/org/orgHeader/Icons/dropDownIcon.svg b/src/pages/tickets/org/orgHeader/Icons/dropDownIcon.svg new file mode 100644 index 00000000..a2cc09b8 --- /dev/null +++ b/src/pages/tickets/org/orgHeader/Icons/dropDownIcon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/pages/tickets/org/orgHeader/index.tsx b/src/pages/tickets/org/orgHeader/index.tsx index a025b7be..e8883144 100644 --- a/src/pages/tickets/org/orgHeader/index.tsx +++ b/src/pages/tickets/org/orgHeader/index.tsx @@ -1,22 +1,25 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect, useRef } 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 { GetValue, coding_languages } from 'people/utils/languageLabelStyle'; +import { colors } from 'config'; import { OrgBountyHeaderProps } from '../../../../people/interfaces'; import { useStores } from '../../../../store'; import addBounty from './Icons/addBounty.svg'; +import dropdown from './Icons/dropDownIcon.svg'; import searchIcon from './Icons/searchIcon.svg'; import file from './Icons/file.svg'; import checkboxImage from './Icons/checkboxImage.svg'; import githubIcon from './Icons/githubIcon.svg'; import websiteIcon from './Icons/websiteIcon.svg'; +const Coding_Languages = GetValue(coding_languages); interface styledProps { color?: any; } - +const color = colors['light']; const Header = styled.div` display: flex; height: 130px; @@ -69,11 +72,8 @@ const FiltersRight = styled.span` const SkillContainer = styled.span` padding: 10px 0px; align-items: center; - gap: 4px; -`; -const Skill = styled.select` - border: none; - background-color: transparent; + display: flex; + position: relative; `; const Button = styled.button` @@ -120,7 +120,7 @@ const UrlButton = styled.button` letter-spacing: 0.14px; `; -const Label = styled.label` +const FilterLabel = styled.label` color: var(--Main-bottom-icons, #5f6368); font-family: Barlow; font-size: 15px; @@ -160,14 +160,12 @@ const SearchBar = styled.input` `; const SoryByContainer = styled.span` + padding: 10px 0px; + display: flex; justify-content: center; align-items: center; - gap: 4px; -`; -const SortBy = styled.select` - background-color: transparent; - border: none; `; + const NumberOfBounties = styled.div` height: 23px; padding: 1.5px 983.492px 1.5px 10px; @@ -204,6 +202,95 @@ const Img = styled.img` padding-bottom: 10px; `; +const SkillFilter = styled.div` + width: 480px; + height: 280px; + background-color: white; + position: absolute; + top: 50px; + z-index: 999; + border-radius: 0px 0px 6px 6px; + border-right: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid rgba(0, 0, 0, 0.1); + background: #fff; + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.12); + + /* border-top: 3px solid var(--Primary-blue, #618AFF); + border-top-height: 20px; */ + + ::after { + content: ''; + position: absolute; + left: 0; + right: 380px; + top: 0; + height: 3px; + background: var(--Primary-blue, #618aff); + } +`; +const InternalContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 30px; + padding: 24px 20px 28px 20px; +`; + +const EuiPopOverCheckboxRight = styled.div` + height: auto; + user-select: none; + &.CheckboxOuter > div { + height: 100%; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + column-gap: 56px; + justify-content: center; + .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-image: url('static/checkboxImage.svg'); + } + .euiCheckbox__label { + font-family: 'Barlow'; + font-style: normal; + font-weight: 500; + font-size: 13px; + line-height: 18px; + 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?.blue1}; + } + } + } +`; + +const DropDownButton = styled.button` + border: none; + background-color: transparent; + padding-top: 5px; +`; + +const FiltersLeft = styled.span` + display: flex; + height: 40px; + align-items: flex-start; +`; + const EuiPopOverCheckbox = styled.div` width: 147px; height: auto; @@ -259,6 +346,8 @@ const EuiPopOverCheckbox = styled.div` } `; +const NewStatusContainer = styled.div``; + const StatusContainer = styled.div` width: 70px; height: 48px; @@ -323,8 +412,9 @@ const StatusContainer = styled.div` `; const Status = ['Open', 'Assigned', 'Completed', 'Paid']; -const color = colors['light']; export const OrgHeader = ({ + onChangeLanguage, + checkboxIdToSelectedMapLanguage, onChangeStatus, checkboxIdToSelectedMap, org_uuid, @@ -333,6 +423,7 @@ export const OrgHeader = ({ }: OrgBountyHeaderProps) => { const { main } = useStores(); const [isPostBountyModalOpen, setIsPostBountyModalOpen] = useState(false); + const [filterClick, setFilterClick] = useState(false); const [isStatusPopoverOpen, setIsStatusPopoverOpen] = useState(false); const onButtonClick = async () => { setIsStatusPopoverOpen((isPopoverOpen: any) => !isPopoverOpen); @@ -340,6 +431,7 @@ export const OrgHeader = ({ const closeStatusPopover = () => setIsStatusPopoverOpen(false); const selectedWidget = 'wanted'; + const filterRef = useRef(null); const { website, github } = organizationUrls; const handlePostBountyClick = () => { setIsPostBountyModalOpen(true); @@ -364,7 +456,27 @@ export const OrgHeader = ({ languageString }); } - }, [org_uuid, checkboxIdToSelectedMap]); + }, [org_uuid, checkboxIdToSelectedMap, main, languageString]); + + const handleClick = () => { + setFilterClick(!filterClick); + }; + + useEffect(() => { + const handleWindowClick = (event: MouseEvent) => { + if (filterRef.current && !filterRef.current.contains(event.target as Node)) { + setFilterClick(false); + } + }; + if (filterClick) { + window.addEventListener('click', handleWindowClick); + } else { + window.removeEventListener('click', handleWindowClick); + } + return () => { + window.removeEventListener('click', handleWindowClick); + }; + }, [filterClick]); return ( <> @@ -397,77 +509,104 @@ 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" - > -
+ 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); - }} - /> - -
-
+
+ + ({ + label: `${status}`, + id: status + }))} + idToSelectedMap={checkboxIdToSelectedMap} + onChange={(id: any) => { + onChangeStatus(id); + }} + /> + +
+ + - - + Skill + + {' '} + + + {filterClick ? ( + + + + { + onChangeLanguage(id); + }} + /> + + + + ) : null}
- - - - + + + Sort by:Newest First + + {' '} + + + +
diff --git a/src/people/interfaces.ts b/src/people/interfaces.ts index e3e2ca18..92c4eca5 100644 --- a/src/people/interfaces.ts +++ b/src/people/interfaces.ts @@ -425,7 +425,9 @@ export interface BountyHeaderProps { export interface OrgBountyHeaderProps { onChangeStatus: (number) => void; + onChangeLanguage: (number) => void; checkboxIdToSelectedMap?: any; + checkboxIdToSelectedMapLanguage?: any; languageString?: string; org_uuid?: string; } diff --git a/src/people/widgetViews/__tests__/OrgHeader.spec.tsx b/src/people/widgetViews/__tests__/OrgHeader.spec.tsx index e1c58894..aa8513b7 100644 --- a/src/people/widgetViews/__tests__/OrgHeader.spec.tsx +++ b/src/people/widgetViews/__tests__/OrgHeader.spec.tsx @@ -15,12 +15,12 @@ const MockProps: OrgBountyHeaderProps = { languageString: '', org_uuid: 'clf6qmo4nncmf23du7ng', onChangeStatus: jest.fn(), + onChangeLanguage: jest.fn(), organizationUrls: { github: 'https://github.com/stakwork/sphinx-tribes', website: 'https://ecurrencyhodler.com/' } }; - describe('OrgHeader Component', () => { beforeEach(() => { jest.spyOn(mainStore, 'getSpecificOrganizationBounties').mockReset(); @@ -34,7 +34,7 @@ describe('OrgHeader Component', () => { render(); expect(screen.getByText('Post a Bounty')).toBeInTheDocument(); expect(screen.getByText('Status')).toBeInTheDocument(); - expect(screen.getByLabelText('Skill')).toBeInTheDocument(); + expect(screen.getByText('Skill')).toBeInTheDocument(); expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); expect(screen.getByText(/Bounties/i)).toBeInTheDocument(); }); @@ -42,6 +42,7 @@ describe('OrgHeader Component', () => { it('opens the PostModal on "Post a Bounty" button click', async () => { render(); fireEvent.click(screen.getByText('Post a Bounty')); + // You can add further assertions here to check the modal is open }); it('displays the correct number of bounties', () => {