diff --git a/src/pages/superadmin/header/FeatureBountyModal.tsx b/src/pages/superadmin/header/FeatureBountyModal.tsx new file mode 100644 index 00000000..2173e1e3 --- /dev/null +++ b/src/pages/superadmin/header/FeatureBountyModal.tsx @@ -0,0 +1,130 @@ +import React, { useState } from 'react'; +import { nonWidgetConfigs } from 'people/utils/Constants'; +import { useIsMobile } from 'hooks/uiHooks'; +import { InvoiceForm, InvoiceInput, InvoiceLabel } from 'people/utils/style'; +import styled from 'styled-components'; +import { BudgetButton } from 'people/widgetViews/workspace/style'; +import { Modal } from '../../../components/common'; +import { bountyStore } from '../../../store/bountyStore'; + +interface FeatureBountyProps { + open: boolean; + close: () => void; + addToast?: (title: string, color: 'success' | 'error') => void; +} + +const ModalTitle = styled.h3` + font-size: 1.9rem; + font-weight: bolder; + margin-bottom: 20px; +`; + +const Wrapper = styled.div` + width: 100%; + display: flex; + flex-direction: column; + padding: 40px 50px; +`; + +const FeatureBountyModal = (props: FeatureBountyProps) => { + const isMobile = useIsMobile(); + const { open, close, addToast } = props; + const [loading, setLoading] = useState(false); + const [bountyUrl, setBountyUrl] = useState(''); + const config = nonWidgetConfigs['workspaceusers']; + + const handleAddFeaturedBounty = async () => { + setLoading(true); + try { + const bountyId = bountyStore.getBountyIdFromURL(bountyUrl); + + if (!bountyId) { + if (addToast) addToast('Invalid bounty URL format', 'error'); + return; + } + + if (bountyStore.hasBounty(bountyId)) { + if (addToast) addToast('Bounty already in featured list', 'error'); + return; + } + + bountyStore.addFeaturedBounty(bountyId); + + if (addToast) addToast('Bounty added to featured list', 'success'); + setBountyUrl(''); + close(); + } catch (error) { + if (addToast) addToast('Could not add bounty to featured list', 'error'); + } finally { + setLoading(false); + } + }; + + return ( + <> + + + Featured Bounties + + + URL of the feature bounty + + ) => setBountyUrl(e.target.value)} + placeholder="Enter bounty URL" + /> + + + Confirm + + + + + ); +}; + +export default FeatureBountyModal; diff --git a/src/pages/superadmin/header/HeaderStyles.tsx b/src/pages/superadmin/header/HeaderStyles.tsx index ff9d9e69..d1d952b2 100644 --- a/src/pages/superadmin/header/HeaderStyles.tsx +++ b/src/pages/superadmin/header/HeaderStyles.tsx @@ -87,6 +87,22 @@ export const ExportButton = styled.button` box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.06); margin-right: 10px; `; + +export const FeaturedButton = styled.button` + width: auto; + padding: 8px 16px; + height: 40px; + justify-content: center; + align-items: center; + gap: 6px; + border-radius: 6px; + border: 1px solid var(--Input-Outline-1, #d0d5d8); + background: var(--White, #fff); + box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.06); + margin-right: 10px; + margin-top: 1px; +`; + export const ExportText = styled.p` color: var(--Main-bottom-icons, #5f6368); text-align: center; diff --git a/src/pages/superadmin/header/index.tsx b/src/pages/superadmin/header/index.tsx index 5d2baed8..8fdbef25 100644 --- a/src/pages/superadmin/header/index.tsx +++ b/src/pages/superadmin/header/index.tsx @@ -16,13 +16,15 @@ import { Option, CustomButton, WorkspaceOption, - WorkspaceText + WorkspaceText, + FeaturedButton } from './HeaderStyles'; import arrowback from './icons/arrowback.svg'; import arrowforward from './icons/arrowforward.svg'; import expand_more from './icons/expand_more.svg'; import App from './components/Calendar/App'; import InviteModal from './InviteModal'; +import FeatureBountyModal from './FeatureBountyModal'; interface HeaderProps { startDate?: number; @@ -51,6 +53,8 @@ export const Header = ({ const [workspaceText, setWorkspaceText] = useState('Workspaces ...'); const [workspaces, setWorkspaces] = useState([]); const [openInvite, setOpenInvite] = useState(false); + const [openFeatureBounty, setOpenFeatureBounty] = useState(false); + const formatUnixDate = (unixDate: number) => { const formatString = 'DD MMM YYYY'; if (startDate !== undefined && endDate !== undefined) { @@ -85,6 +89,10 @@ export const Header = ({ } }; + const toggleFeatureBountyModal = () => { + setOpenFeatureBounty(!openFeatureBounty); + }; + const toggleModal = () => { setOpenInvite(!openInvite); }; @@ -208,6 +216,9 @@ export const Header = ({ ) : null} + toggleFeatureBountyModal()}> + Featured Bounty + toggleModal()}> Invite Users @@ -274,6 +285,11 @@ export const Header = ({ /> )} + ); }; diff --git a/src/store/bountyStore.ts b/src/store/bountyStore.ts new file mode 100644 index 00000000..7ee08342 --- /dev/null +++ b/src/store/bountyStore.ts @@ -0,0 +1,88 @@ +import { makeAutoObservable } from 'mobx'; + +interface FeaturedBounty { + bountyId: string; + sequence: number; +} + +class BountyStore { + featuredBounties: FeaturedBounty[] = []; + + constructor() { + makeAutoObservable(this); + this.loadFromStorage(); + } + + private loadFromStorage(): void { + try { + const saved = localStorage.getItem('featuredBounties'); + if (saved) { + this.featuredBounties = JSON.parse(saved); + } + } catch (error) { + console.error('Error loading from storage:', error); + } + } + + private saveToStorage(): void { + try { + localStorage.setItem('featuredBounties', JSON.stringify(this.featuredBounties)); + } catch (error) { + console.error('Error saving to storage:', error); + } + } + + getBountyIdFromURL(url: string): string | null { + const match = url.match(/\/bounty\/(\d+)$/); + return match ? match[1] : null; + } + + addFeaturedBounty(bountyId: string): void { + const nextSequence = this.featuredBounties.length + 1; + const exists = this.featuredBounties.some((b: FeaturedBounty) => b.bountyId === bountyId); + + if (!exists) { + this.featuredBounties.push({ + bountyId, + sequence: nextSequence + }); + this.saveToStorage(); + } + } + + removeFeaturedBounty(bountyId: string): void { + this.featuredBounties = this.featuredBounties.filter( + (b: FeaturedBounty) => b.bountyId !== bountyId + ); + this.featuredBounties.forEach((bounty: FeaturedBounty, index: number) => { + bounty.sequence = index + 1; + }); + this.saveToStorage(); + } + + getFeaturedBounties(): FeaturedBounty[] { + return this.featuredBounties + .slice() + .sort((a: FeaturedBounty, b: FeaturedBounty) => a.sequence - b.sequence); + } + + updateSequence(bountyId: string, newSequence: number): void { + const bounty = this.featuredBounties.find((b: FeaturedBounty) => b.bountyId === bountyId); + if (bounty) { + bounty.sequence = newSequence; + this.saveToStorage(); + } + } + + hasBounty(bountyId: string): boolean { + const exists = this.featuredBounties.some((b: FeaturedBounty) => b.bountyId === bountyId); + return exists; + } + + clearAllBounties(): void { + this.featuredBounties = []; + this.saveToStorage(); + } +} + +export const bountyStore = new BountyStore();