Skip to content

Commit

Permalink
Merge pull request #749 from aliraza556/feature-featured-bounties-adm…
Browse files Browse the repository at this point in the history
…in-modal

Add Featured Bounties Management Modal for Admin and `featured_bounty` store
  • Loading branch information
humansinstitute authored Dec 11, 2024
2 parents 2897787 + 5b4b8ed commit b83ce48
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 1 deletion.
130 changes: 130 additions & 0 deletions src/pages/superadmin/header/FeatureBountyModal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Modal
visible={open}
style={{
height: '100%',
flexDirection: 'column',
width: '100%',
alignItems: `${isMobile ? '' : 'center'}`,
justifyContent: `${isMobile ? '' : 'center'}`,
overflowY: 'hidden'
}}
envStyle={{
marginTop: isMobile ? 64 : 0,
background: 'white',
zIndex: 20,
...(config?.modalStyle ?? {}),
maxHeight: '100%',
borderRadius: '10px'
}}
overlayClick={close}
bigCloseImage={close}
bigCloseImageStyle={{
top: '1.6rem',
right: `${isMobile ? '0rem' : '-1.25rem'}`,
background: '#000',
borderRadius: '50%'
}}
>
<Wrapper>
<ModalTitle>Featured Bounties</ModalTitle>
<InvoiceForm>
<InvoiceLabel
style={{
display: 'block'
}}
>
URL of the feature bounty
</InvoiceLabel>
<InvoiceInput
type="text"
style={{
width: '100%'
}}
value={bountyUrl}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setBountyUrl(e.target.value)}
placeholder="Enter bounty URL"
/>
</InvoiceForm>
<BudgetButton
disabled={!bountyUrl}
style={{
borderRadius: '8px',
marginTop: '12px',
color: !loading && bountyUrl ? '#FFF' : 'rgba(142, 150, 156, 0.85)',
background: !loading && bountyUrl ? '#9157F6' : 'rgba(0, 0, 0, 0.04)'
}}
onClick={handleAddFeaturedBounty}
>
Confirm
</BudgetButton>
</Wrapper>
</Modal>
</>
);
};

export default FeatureBountyModal;
16 changes: 16 additions & 0 deletions src/pages/superadmin/header/HeaderStyles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 17 additions & 1 deletion src/pages/superadmin/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,6 +53,8 @@ export const Header = ({
const [workspaceText, setWorkspaceText] = useState<string>('Workspaces ...');
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [openInvite, setOpenInvite] = useState(false);
const [openFeatureBounty, setOpenFeatureBounty] = useState(false);

const formatUnixDate = (unixDate: number) => {
const formatString = 'DD MMM YYYY';
if (startDate !== undefined && endDate !== undefined) {
Expand Down Expand Up @@ -85,6 +89,10 @@ export const Header = ({
}
};

const toggleFeatureBountyModal = () => {
setOpenFeatureBounty(!openFeatureBounty);
};

const toggleModal = () => {
setOpenInvite(!openInvite);
};
Expand Down Expand Up @@ -208,6 +216,9 @@ export const Header = ({
) : null}
</LeftWrapper>
<RightWrapper>
<FeaturedButton onClick={() => toggleFeatureBountyModal()}>
<ExportText>Featured Bounty</ExportText>
</FeaturedButton>
<ExportButton onClick={() => toggleModal()}>
<ExportText>Invite Users</ExportText>
</ExportButton>
Expand Down Expand Up @@ -274,6 +285,11 @@ export const Header = ({
/>
)}
<InviteModal addToast={addToast} open={openInvite} close={toggleModal} />
<FeatureBountyModal
addToast={addToast}
open={openFeatureBounty}
close={toggleFeatureBountyModal}
/>
</Container>
);
};
88 changes: 88 additions & 0 deletions src/store/bountyStore.ts
Original file line number Diff line number Diff line change
@@ -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();

0 comments on commit b83ce48

Please sign in to comment.