From 80f5febb518be45f8519288780929fbccb1d02c5 Mon Sep 17 00:00:00 2001 From: chamikaJ Date: Wed, 23 Jul 2025 10:33:55 +0530 Subject: [PATCH] feat(antd-imports): establish centralized import rules for Ant Design components - Introduced a new rules file for Ant Design component imports, enforcing the use of centralized imports from `@antd-imports` to enhance tree-shaking, maintainability, and performance. - Updated various components to replace direct imports from 'antd' and '@ant-design/icons' with the centralized import path. - Refactored existing components to utilize memoization and callbacks for improved performance and readability. - Enhanced the billing and configuration components with updated styles and improved user experience. --- .cursor/rules/antd-components.mdc | 237 +++++++++++++++ .../billing/billing-tables/charges-table.tsx | 12 +- .../admin-center/billing/current-bill.tsx | 116 ++++--- .../current-plan-details.tsx | 285 +++++++++++------- .../drawers/upgrade-plans/upgrade-plans.tsx | 55 ++-- .../configuration/configuration.tsx | 167 +++++----- .../pages/admin-center/billing/billing.tsx | 43 +-- worklenz-frontend/src/shared/antd-imports.ts | 2 + 8 files changed, 639 insertions(+), 278 deletions(-) create mode 100644 .cursor/rules/antd-components.mdc diff --git a/.cursor/rules/antd-components.mdc b/.cursor/rules/antd-components.mdc new file mode 100644 index 00000000..756179bd --- /dev/null +++ b/.cursor/rules/antd-components.mdc @@ -0,0 +1,237 @@ +--- +alwaysApply: true +--- +# Ant Design Import Rules for Worklenz + +## 🚨 CRITICAL: Always Use Centralized Imports + +**NEVER import Ant Design components directly from 'antd' or '@ant-design/icons'** + +### ✅ Correct Import Pattern +```typescript +import { Button, Input, Select, EditOutlined, PlusOutlined } from '@antd-imports'; +// or +import { Button, Input, Select, EditOutlined, PlusOutlined } from '@/shared/antd-imports'; +``` + +### ❌ Forbidden Import Patterns +```typescript +// NEVER do this: +import { Button, Input, Select } from 'antd'; +import { EditOutlined, PlusOutlined } from '@ant-design/icons'; +``` + +## Why This Rule Exists + +### Benefits of Centralized Imports: +- **Better Tree-Shaking**: Optimized bundle size through centralized management +- **Consistent React Context**: Proper context sharing across components +- **Type Safety**: Centralized TypeScript definitions +- **Maintainability**: Single source of truth for all Ant Design imports +- **Performance**: Reduced bundle size and improved loading times + +## What's Available in `@antd-imports` + +### Core Components +- **Layout**: Layout, Row, Col, Flex, Divider, Space +- **Navigation**: Menu, Tabs, Breadcrumb, Pagination +- **Data Entry**: Input, Select, DatePicker, TimePicker, Form, Checkbox, InputNumber +- **Data Display**: Table, List, Card, Tag, Avatar, Badge, Progress, Statistic +- **Feedback**: Modal, Drawer, Alert, Message, Notification, Spin, Skeleton, Result +- **Other**: Button, Typography, Tooltip, Popconfirm, Dropdown, ConfigProvider + +### Icons +Common icons including: EditOutlined, DeleteOutlined, PlusOutlined, MoreOutlined, CheckOutlined, CloseOutlined, CalendarOutlined, UserOutlined, TeamOutlined, and many more. + +### Utilities +- **appMessage**: Centralized message utility +- **appNotification**: Centralized notification utility +- **antdConfig**: Default Ant Design configuration +- **taskManagementAntdConfig**: Task-specific configuration + +## Implementation Guidelines + +### When Creating New Components: +1. **Always** import from `@antd-imports` or `@/shared/antd-imports` +2. Use `appMessage` and `appNotification` for user feedback +3. Apply `antdConfig` for consistent styling +4. Use `taskManagementAntdConfig` for task-related components + +### When Refactoring Existing Code: +1. Replace direct 'antd' imports with `@antd-imports` +2. Replace direct '@ant-design/icons' imports with `@antd-imports` +3. Update any custom message/notification calls to use the utilities + +### File Location +The centralized import file is located at: `worklenz-frontend/src/shared/antd-imports.ts` + +## Examples + +### Component Creation +```typescript +import React from 'react'; +import { Button, Input, Modal, EditOutlined, appMessage } from '@antd-imports'; + +const MyComponent = () => { + const handleClick = () => { + appMessage.success('Operation completed!'); + }; + + return ( + + ); +}; +``` + +### Form Implementation +```typescript +import { Form, Input, Select, Button, DatePicker } from '@antd-imports'; + +const MyForm = () => { + return ( +
+ + + + + + + + { {t('freePlan')}} + title={{t('freePlan', 'Free Plan')}} onClick={() => setSelectedCard(paddlePlans.FREE)} >
$ 0.00 - {t('freeForever')} + {t('freeForever', 'Free Forever')} - {t('bestForPersonalUse')} + {t('bestForPersonalUse', 'Best for Personal Use')}
- {renderFeature(`${plans.free_tier_storage} ${t('storage')}`)} - {renderFeature(`${plans.projects_limit} ${t('projects')}`)} - {renderFeature(`${plans.team_member_limit} ${t('teamMembers')}`)} + {renderFeature(`${plans.free_tier_storage} ${t('storage', 'Storage')}`)} + {renderFeature(`${plans.projects_limit} ${t('projects', 'Projects')}`)} + {renderFeature(`${plans.team_member_limit} ${t('teamMembers', 'Team Members')}`)}
@@ -401,9 +412,9 @@ const UpgradePlans = () => { hoverable title={ - {t('annualPlan')}{' '} + {t('annualPlan', 'Annual Plan')}{' '} - {t('tag')} + {t('tag', 'Popular')} } @@ -429,16 +440,16 @@ const UpgradePlans = () => { - {t('billedAnnually')} + {t('billedAnnually', 'Billed Annually')}
- {renderFeature(t('startupText01'))} - {renderFeature(t('startupText02'))} - {renderFeature(t('startupText03'))} - {renderFeature(t('startupText04'))} - {renderFeature(t('startupText05'))} + {renderFeature(t('startupText01', 'Unlimited Projects'))} + {renderFeature(t('startupText02', 'Unlimited Team Members'))} + {renderFeature(t('startupText03', 'Unlimited Storage'))} + {renderFeature(t('startupText04', 'Priority Support'))} + {renderFeature(t('startupText05', 'Advanced Analytics'))}
diff --git a/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx b/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx index afa5b51a..ffb1e806 100644 --- a/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx +++ b/worklenz-frontend/src/components/admin-center/configuration/configuration.tsx @@ -1,5 +1,5 @@ -import { Button, Card, Col, Divider, Form, Input, notification, Row, Select } from 'antd'; -import React, { useEffect, useState } from 'react'; +import { Button, Card, Col, Divider, Form, Input, Row, Select } from '@/shared/antd-imports'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { RootState } from '../../../app/store'; import { useAppSelector } from '@/hooks/useAppSelector'; import { IBillingConfigurationCountry } from '@/types/admin-center/country.types'; @@ -7,14 +7,15 @@ import { adminCenterApiService } from '@/api/admin-center/admin-center.api.servi import { IBillingConfiguration } from '@/types/admin-center/admin-center.types'; import logger from '@/utils/errorLogger'; -const Configuration: React.FC = () => { +const Configuration: React.FC = React.memo(() => { const themeMode = useAppSelector((state: RootState) => state.themeReducer.mode); const [countries, setCountries] = useState([]); const [configuration, setConfiguration] = useState(); const [loading, setLoading] = useState(false); const [form] = Form.useForm(); - const fetchCountries = async () => { + + const fetchCountries = useCallback(async () => { try { const res = await adminCenterApiService.getCountries(); if (res.done) { @@ -23,61 +24,85 @@ const Configuration: React.FC = () => { } catch (error) { logger.error('Error fetching countries:', error); } - }; + }, []); - const fetchConfiguration = async () => { + const fetchConfiguration = useCallback(async () => { const res = await adminCenterApiService.getBillingConfiguration(); if (res.done) { setConfiguration(res.body); form.setFieldsValue(res.body); } - }; + }, [form]); useEffect(() => { fetchCountries(); fetchConfiguration(); - }, []); + }, [fetchCountries, fetchConfiguration]); - const handleSave = async (values: any) => { - try { - setLoading(true); - const res = await adminCenterApiService.updateBillingConfiguration(values); - if (res.done) { - fetchConfiguration(); + const handleSave = useCallback( + async (values: any) => { + try { + setLoading(true); + const res = await adminCenterApiService.updateBillingConfiguration(values); + if (res.done) { + fetchConfiguration(); + } + } catch (error) { + logger.error('Error updating configuration:', error); + } finally { + setLoading(false); } - } catch (error) { - logger.error('Error updating configuration:', error); - } finally { - setLoading(false); - } - }; + }, + [fetchConfiguration] + ); - const countryOptions = countries.map(country => ({ - label: country.name, - value: country.id, - })); + const countryOptions = useMemo( + () => + countries.map(country => ({ + label: country.name, + value: country.id, + })), + [countries] + ); + + const titleStyle = useMemo( + () => ({ + color: `${themeMode === 'dark' ? '#ffffffd9' : '#000000d9'}`, + fontWeight: 500, + fontSize: '16px', + display: 'flex', + gap: '4px', + }), + [themeMode] + ); + + const dividerTitleStyle = useMemo( + () => ({ + color: `${themeMode === 'dark' ? '#ffffffd9' : '#000000d9'}`, + fontWeight: 600, + fontSize: '16px', + display: 'flex', + gap: '4px', + }), + [themeMode] + ); + + const cardStyle = useMemo(() => ({ marginTop: '16px' }), []); + const colStyle = useMemo(() => ({ padding: '0 12px', height: '86px' }), []); + const dividerStyle = useMemo(() => ({ margin: '16px 0' }), []); + const buttonColStyle = useMemo(() => ({ paddingLeft: '12px' }), []); + + const handlePhoneInput = useCallback((e: React.FormEvent) => { + const input = e.target as HTMLInputElement; + input.value = input.value.replace(/[^0-9]/g, ''); + }, []); return (
- - Billing Details - - } - style={{ marginTop: '16px' }} - > + Billing Details} style={cardStyle}> - + { }, ]} > - + - + { }, ]} > - + - + { }, ]} > - { - const input = e.target as HTMLInputElement; // Type assertion to access 'value' - input.value = input.value.replace(/[^0-9]/g, ''); // Restrict non-numeric input - }} - /> + - - - Company Details - + + Company Details - + - + - + - + - + - + - + - @@ -212,6 +213,8 @@ const Configuration: React.FC = () => {
); -}; +}); + +Configuration.displayName = 'Configuration'; export default Configuration; diff --git a/worklenz-frontend/src/pages/admin-center/billing/billing.tsx b/worklenz-frontend/src/pages/admin-center/billing/billing.tsx index b4eaa864..bc18dcbb 100644 --- a/worklenz-frontend/src/pages/admin-center/billing/billing.tsx +++ b/worklenz-frontend/src/pages/admin-center/billing/billing.tsx @@ -1,32 +1,39 @@ import { PageHeader } from '@ant-design/pro-components'; -import { Tabs, TabsProps } from 'antd'; -import React from 'react'; +import { Tabs, TabsProps } from '@/shared/antd-imports'; +import React, { useMemo } from 'react'; import CurrentBill from '@/components/admin-center/billing/current-bill'; import Configuration from '@/components/admin-center/configuration/configuration'; import { useTranslation } from 'react-i18next'; -const Billing: React.FC = () => { +const Billing: React.FC = React.memo(() => { const { t } = useTranslation('admin-center/current-bill'); - const items: TabsProps['items'] = [ - { - key: '1', - label: t('currentBill'), - children: , - }, - { - key: '2', - label: t('configuration'), - children: , - }, - ]; + const items: TabsProps['items'] = useMemo( + () => [ + { + key: '1', + label: t('currentBill'), + children: , + }, + { + key: '2', + label: t('configuration'), + children: , + }, + ], + [t] + ); + + const pageHeaderStyle = useMemo(() => ({ padding: '16px 0' }), []); return (
- {t('title')}} style={{ padding: '16px 0' }} /> - + {t('title')}} style={pageHeaderStyle} /> +
); -}; +}); + +Billing.displayName = 'Billing'; export default Billing; diff --git a/worklenz-frontend/src/shared/antd-imports.ts b/worklenz-frontend/src/shared/antd-imports.ts index bd0b31bd..253ff847 100644 --- a/worklenz-frontend/src/shared/antd-imports.ts +++ b/worklenz-frontend/src/shared/antd-imports.ts @@ -50,6 +50,7 @@ import { message, notification, theme, + Statistic, } from 'antd'; // Icons - Import commonly used ones @@ -145,6 +146,7 @@ export { message, notification, theme, + Statistic, }; // TypeScript Types - Import commonly used ones