Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…awa-admin into session-timeout
  • Loading branch information
JordanCampbell1 committed Aug 21, 2024
2 parents ae0d10d + 989b154 commit 398d995
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 12 deletions.
68 changes: 68 additions & 0 deletions .github/workflows/check-tsdoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import fs from 'fs/promises'; // Import fs.promises for async operations
import path from 'path';

// List of files to skip
const filesToSkip = [
'index.tsx',
'EventActionItems.tsx',
'OrgPostCard.tsx',
'UsersTableItem.tsx',
'FundCampaignPledge.tsx'
];

// Recursively find all .tsx files, excluding files listed in filesToSkip
async function findTsxFiles(dir) {
let results = [];
try {
const list = await fs.readdir(dir);
for (const file of list) {
const filePath = path.join(dir, file);
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
results = results.concat(await findTsxFiles(filePath));
} else if (
filePath.endsWith('.tsx') &&
!filePath.endsWith('.test.tsx') &&
!filesToSkip.includes(path.relative(dir, filePath))
) {
results.push(filePath);
}
}
} catch (err) {
console.error(`Error reading directory ${dir}: ${err.message}`);
}
return results;
}

// Check if a file contains at least one TSDoc comment
async function containsTsDocComment(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
return /\/\*\*[\s\S]*?\*\//.test(content);
} catch (err) {
console.error(`Error reading file ${filePath}: ${err.message}`);
return false;
}
}

// Main function to run the validation
async function run() {
const dir = process.argv[2] || './src'; // Allow directory path as a command-line argument
const files = await findTsxFiles(dir);
const filesWithoutTsDoc = [];

for (const file of files) {
if (!await containsTsDocComment(file)) {
filesWithoutTsDoc.push(file);
}
}

if (filesWithoutTsDoc.length > 0) {
filesWithoutTsDoc.forEach(file => {
console.error(`No TSDoc comment found in file: ${file}`);
});
process.exit(1);
}
}

run();
3 changes: 3 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ jobs:
CHANGED_FILES: ${{ steps.changed_files.outputs.all_changed_files }}
run: npx eslint ${CHANGED_FILES}

- name: Check for TSDoc comments
run: npm run check-tsdoc # Run the TSDoc check script

- name: Check for localStorage Usage
run: |
chmod +x scripts/githooks/check-localstorage-usage.js
Expand Down
19 changes: 10 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.16.0",
"history": "^5.3.0",
"i18next": "^21.8.14",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
"inquirer": "^8.0.0",
Expand Down Expand Up @@ -68,6 +68,7 @@
"lint:fix": "eslint --fix \"**/*.{ts,tsx}\"",
"format:fix": "prettier --write \"**/*.{ts,tsx,json,scss,css}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,json,scss,css}\"",
"check-tsdoc": "node .github/workflows/check-tsdoc.js",
"typecheck": "tsc --project tsconfig.json --noEmit",
"prepare": "husky install",
"jest-preview": "jest-preview",
Expand Down
30 changes: 30 additions & 0 deletions src/screens/UserPortal/Campaigns/Campaigns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,35 @@ import type { InterfaceUserCampaign } from 'utils/interfaces';
import { currencySymbols } from 'utils/currency';
import Loader from 'components/Loader/Loader';

/**
* The `Campaigns` component displays a list of fundraising campaigns for a specific organization.
* It allows users to search, sort, and view details about each campaign. Users can also add pledges to active campaigns.
*
* @returns The rendered component displaying the campaigns.
*/
const Campaigns = (): JSX.Element => {
// Retrieves translation functions for various namespaces
const { t } = useTranslation('translation', {
keyPrefix: 'userCampaigns',
});
const { t: tCommon } = useTranslation('common');
const { t: tErrors } = useTranslation('errors');

// Retrieves stored user ID from local storage
const { getItem } = useLocalStorage();
const userId = getItem('userId');

// Extracts organization ID from the URL parameters
const { orgId } = useParams();
if (!orgId || !userId) {
// Redirects to the homepage if orgId or userId is missing
return <Navigate to={'/'} replace />;
}

// Navigation hook to programmatically navigate between routes
const navigate = useNavigate();

// State for managing search term, campaigns, selected campaign, modal state, and sorting order
const [searchTerm, setSearchTerm] = useState<string>('');
const [campaigns, setCampaigns] = useState<InterfaceUserCampaign[]>([]);
const [selectedCampaign, setSelectedCampaign] =
Expand All @@ -46,6 +58,7 @@ const Campaigns = (): JSX.Element => {
'fundingGoal_ASC' | 'fundingGoal_DESC' | 'endDate_ASC' | 'endDate_DESC'
>('endDate_DESC');

// Fetches campaigns based on the organization ID, search term, and sorting order
const {
data: campaignData,
loading: campaignLoading,
Expand All @@ -68,24 +81,35 @@ const Campaigns = (): JSX.Element => {
},
});

/**
* Opens the modal for adding a pledge to a selected campaign.
*
* @param campaign - The campaign to which the user wants to add a pledge.
*/
const openModal = (campaign: InterfaceUserCampaign): void => {
setSelectedCampaign(campaign);
setModalState(true);
};

/**
* Closes the modal and clears the selected campaign.
*/
const closeModal = (): void => {
setModalState(false);
setSelectedCampaign(null);
};

// Updates the campaigns state when the fetched campaign data changes
useEffect(() => {
if (campaignData) {
setCampaigns(campaignData.getFundraisingCampaigns);
}
}, [campaignData]);

// Renders a loader while campaigns are being fetched
if (campaignLoading) return <Loader size="xl" />;
if (campaignError) {
// Displays an error message if there is an issue loading the campaigns
return (
<div className={`${styles.container} bg-white rounded-4 my-3`}>
<div className={styles.message} data-testid="errorMsg">
Expand All @@ -100,9 +124,11 @@ const Campaigns = (): JSX.Element => {
);
}

// Renders the campaign list and UI elements for searching, sorting, and adding pledges
return (
<>
<div className={`${styles.btnsContainer} gap-4 flex-wrap`}>
{/* Search input field and button */}
<div className={`${styles.input} mb-1`}>
<Form.Control
type="name"
Expand All @@ -124,6 +150,7 @@ const Campaigns = (): JSX.Element => {
</div>
<div className="d-flex gap-4 mb-1">
<div className="d-flex justify-space-between">
{/* Dropdown menu for sorting campaigns */}
<Dropdown>
<Dropdown.Toggle
variant="success"
Expand Down Expand Up @@ -163,6 +190,7 @@ const Campaigns = (): JSX.Element => {
</Dropdown>
</div>
<div>
{/* Button to navigate to the user's pledges */}
<Button
variant="success"
data-testid="myPledgesBtn"
Expand All @@ -178,6 +206,7 @@ const Campaigns = (): JSX.Element => {
</div>
{campaigns.length < 1 ? (
<Stack height="100%" alignItems="center" justifyContent="center">
{/* Displayed if no campaigns are found */}
{t('noCampaigns')}
</Stack>
) : (
Expand Down Expand Up @@ -254,6 +283,7 @@ const Campaigns = (): JSX.Element => {
))
)}

{/* Modal for adding pledges to campaigns */}
<PledgeModal
isOpen={modalState}
hide={closeModal}
Expand Down
44 changes: 43 additions & 1 deletion src/screens/UserPortal/Campaigns/PledgeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import {
} from '@mui/material';
import { USER_DETAILS } from 'GraphQl/Queries/Queries';

/**
* Interface representing the properties for the `PledgeModal` component.
*/
export interface InterfacePledgeModal {
isOpen: boolean;
hide: () => void;
Expand All @@ -34,6 +37,20 @@ export interface InterfacePledgeModal {
endDate: Date;
mode: 'create' | 'edit';
}

/**
* `PledgeModal` is a React component that allows users to create or edit a pledge for a specific campaign.
* It displays a form with inputs for pledge details such as amount, currency, dates, and users involved in the pledge.
*
* @param isOpen - Determines if the modal is visible or hidden.
* @param hide - Function to close the modal.
* @param campaignId - The ID of the campaign for which the pledge is being made.
* @param userId - The ID of the user making or editing the pledge.
* @param pledge - The current pledge information if in edit mode, or null if creating a new pledge.
* @param refetchPledge - Function to refresh the pledge data after a successful operation.
* @param endDate - The maximum date allowed for the pledge's end date, based on the campaign's end date.
* @param mode - Specifies whether the modal is used for creating a new pledge or editing an existing one.
*/
const PledgeModal: React.FC<InterfacePledgeModal> = ({
isOpen,
hide,
Expand All @@ -44,22 +61,31 @@ const PledgeModal: React.FC<InterfacePledgeModal> = ({
endDate,
mode,
}) => {
// Translation functions to support internationalization
const { t } = useTranslation('translation', {
keyPrefix: 'pledges',
});
const { t: tCommon } = useTranslation('common');

// State to manage the form inputs for the pledge
const [formState, setFormState] = useState<InterfaceCreatePledge>({
pledgeUsers: [],
pledgeAmount: pledge?.amount ?? 0,
pledgeCurrency: pledge?.currency ?? 'USD',
pledgeEndDate: new Date(pledge?.endDate ?? new Date()),
pledgeStartDate: new Date(pledge?.startDate ?? new Date()),
});

// State to manage the list of pledgers (users who are part of the pledge)
const [pledgers, setPledgers] = useState<InterfacePledger[]>([]);

// Mutation to update an existing pledge
const [updatePledge] = useMutation(UPDATE_PLEDGE);

// Mutation to create a new pledge
const [createPledge] = useMutation(CREATE_PlEDGE);

// Effect to update the form state when the pledge prop changes (e.g., when editing a pledge)
useEffect(() => {
setFormState({
pledgeUsers: pledge?.users ?? [],
Expand All @@ -70,6 +96,7 @@ const PledgeModal: React.FC<InterfacePledgeModal> = ({
});
}, [pledge]);

// Destructuring the form state for easier access
const {
pledgeUsers,
pledgeAmount,
Expand All @@ -78,12 +105,14 @@ const PledgeModal: React.FC<InterfacePledgeModal> = ({
pledgeEndDate,
} = formState;

// Query to get the user details based on the userId prop
const { data: userData } = useQuery(USER_DETAILS, {
variables: {
id: userId,
},
});

// Effect to update the pledgers state when user data is fetched
useEffect(() => {
if (userData) {
setPledgers([
Expand All @@ -97,6 +126,13 @@ const PledgeModal: React.FC<InterfacePledgeModal> = ({
}
}, [userData]);

/**
* Handler function to update an existing pledge.
* It compares the current form state with the existing pledge and updates only the changed fields.
*
* @param e - The form submission event.
* @returns A promise that resolves when the pledge is successfully updated.
*/
/*istanbul ignore next*/
const updatePledgeHandler = useCallback(
async (e: ChangeEvent<HTMLFormElement>): Promise<void> => {
Expand Down Expand Up @@ -140,7 +176,13 @@ const PledgeModal: React.FC<InterfacePledgeModal> = ({
[formState, pledge],
);

// Function to create a new pledge
/**
* Handler function to create a new pledge.
* It collects the form data and sends a request to create a pledge with the specified details.
*
* @param e - The form submission event.
* @returns A promise that resolves when the pledge is successfully created.
*/
const createPledgeHandler = useCallback(
async (e: ChangeEvent<HTMLFormElement>): Promise<void> => {
try {
Expand Down
Loading

0 comments on commit 398d995

Please sign in to comment.