Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Featured Bounties Management Modal for Admin and featured_bounty store #749

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Loading