Skip to content

Commit

Permalink
Add project withdrawal functionality (#490)
Browse files Browse the repository at this point in the history
  • Loading branch information
AmirAgassi authored Feb 18, 2025
2 parents bf33b1a + 3de144d commit 6ef0872
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 50 deletions.
162 changes: 112 additions & 50 deletions frontend/src/components/ProjectCard/ProjectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,128 @@ import { ExtendedProjectResponse } from '@/services/project';
import { formatUnixTimestamp } from '@/utils/date';
import { Badge, Button, Card } from '@components';
import { ReactNode, useNavigate } from '@tanstack/react-router';
import { FC } from 'react';
import { FC, useState } from 'react';
import { ProjectStatusEnum, updateProjectStatus } from '@/services/projects';
import { WithdrawProjectModal } from '../WithdrawProjectModal/WithdrawProjectModal';
import { useAuth } from '@/contexts/AuthContext';
import { useNotification } from '@/contexts/NotificationContext';

export interface ProjectCardProps {
data: ExtendedProjectResponse;
}

export const ProjectCard: FC<ProjectCardProps> = ({ data }) => {
const navigate = useNavigate();
const { accessToken } = useAuth();
const { push } = useNotification();
const [showWithdrawModal, setShowWithdrawModal] = useState(false);
const [isWithdrawing, setIsWithdrawing] = useState(false);

const handleWithdraw = async () => {
if (!accessToken) return;

try {
setIsWithdrawing(true);
await updateProjectStatus(accessToken, data.id, ProjectStatusEnum.Withdrawn);
push({
message: 'Project withdrawn successfully',
level: 'success',
});
window.location.reload();
} catch (error: any) {
console.error('Failed to withdraw project:', error);

if (error?.response?.status === 401) {
push({
message: 'Your session has expired. Please sign in again.',
level: 'error',
});
} else if (error?.response?.status === 403) {
push({
message: 'You do not have permission to withdraw this project.',
level: 'error',
});
} else {
push({
message: 'Unable to withdraw your project. Please try again or contact support if the issue persists.',
level: 'error',
});
}
} finally {
setIsWithdrawing(false);
setShowWithdrawModal(false);
}
};

const canWithdraw = data.status === ProjectStatusEnum.Pending;

return (
<Card>
<div className="flex justify-between items-center">
<div className="h-full">
<h1 className="align-bottom h-full">{data.title}</h1>
<>
<Card>
<div className="flex justify-between items-center">
<div className="h-full">
<h1 className="align-bottom h-full">{data.title}</h1>
</div>
<div className="flex items-center gap-3">
{canWithdraw && (
<Button
variant="outline"
onClick={() => setShowWithdrawModal(true)}
>
Withdraw
</Button>
)}
{data.status === 'draft' && (
<Button
onClick={() =>
navigate({
to: `/user/project/${data.id}/form`,
})
}
>
Edit Draft Project
</Button>
)}
{data.status !== 'draft' && (
<Button
onClick={() =>
navigate({
to: `/user/project/${data.id}/view`,
})
}
>
View
</Button>
)}
</div>
</div>
<div className="flex items-center gap-3">
{/* TODO: add this in post MVP */}
{/* <Button variant="outline">Withdraw</Button> */}
{data.status === 'draft' && (
<Button
onClick={() =>
navigate({
to: `/user/project/${data.id}/form`,
})
}
>
Edit Draft Project
</Button>
)}
{data.status !== 'draft' && (
<Button
onClick={() =>
navigate({
to: `/user/project/${data.id}/view`,
})
}
>
View
</Button>
)}
<div className="h-[1px] bg-gray-300 my-4"></div>
<div className="flex items-center justify-between">
<InfoSection label="Company Name">
{data.companyName}
</InfoSection>
<InfoSection label="Status">
<Badge text={data.status} />
</InfoSection>
<InfoSection label="Date Submitted">
{formatUnixTimestamp(data.updatedAt)}
</InfoSection>
<InfoSection label="Documents Uploaded">
{`${data.documentCount} Documents`}
</InfoSection>
<InfoSection label="Team Members">
{`${data.teamMemberCount} Members`}
</InfoSection>
</div>
</div>
<div className="h-[1px] bg-gray-300 my-4"></div>
<div className="flex items-center justify-between">
<InfoSection label="Company Name">
{data.companyName}
</InfoSection>
<InfoSection label="Status">
<Badge text={data.status} />
</InfoSection>
<InfoSection label="Date Submitted">
{formatUnixTimestamp(data.updatedAt)}
</InfoSection>
<InfoSection label="Documents Uploaded">
{`${data.documentCount} Documents`}
</InfoSection>
<InfoSection label="Team Members">
{`${data.teamMemberCount} Members`}
</InfoSection>
</div>
</Card>
</Card>

<WithdrawProjectModal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
onConfirm={handleWithdraw}
isLoading={isWithdrawing}
/>
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Dialog } from '@headlessui/react';
import { FiX } from 'react-icons/fi';

interface WithdrawProjectModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => Promise<void>;
isLoading: boolean;
}

export function WithdrawProjectModal({
isOpen,
onClose,
onConfirm,
isLoading
}: WithdrawProjectModalProps) {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await onConfirm();
};

return (
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 flex items-center justify-center p-4">
<Dialog.Panel className="mx-auto w-[400px] rounded-lg bg-white">
<div className="flex items-center justify-between border-b border-gray-200 px-4 py-3">
<Dialog.Title className="text-lg font-medium">
Withdraw Application?
</Dialog.Title>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-500"
>
<FiX className="h-5 w-5" />
</button>
</div>

<div className="p-4">
<p className="text-gray-600">
Your application will be withdrawn from consideration for funding and you will no longer be able to make any changes.
</p>

<div className="mt-6 flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
>
Cancel
</button>
<button
type="button"
onClick={handleSubmit}
disabled={isLoading}
className="px-4 py-2 text-sm font-medium text-white bg-gray-900 rounded-md hover:bg-gray-800 disabled:bg-gray-400"
>
{isLoading ? 'Processing...' : 'Yes, withdraw it'}
</button>
</div>
</div>
</Dialog.Panel>
</div>
</Dialog>
);
}

0 comments on commit 6ef0872

Please sign in to comment.