diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c2e059e --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +VITE_NODE_ENV=development +VITE_APP_BASE_URL=http://localhost:3001 +VITE_APP_URL=http://localhost:5173 +VITE_APP_NETWORK=sepolia +VITE_APP_BLOCK_EXPLORER=https://sepolia.arbiscan.io/ + +# WalletConnect and RainbowKit Project ID +VITE_APP_RAINBOW_PROJECT_ID= +VITE_APP_WALLETCONNECT_PROJECT_ID= + +# Contract Factory Addresses +VITE_APP_TEAM_POINTS_FACTORY_ADDRESS= +VITE_APP_TEAM_POINTS_FACTORY_ADDRESS_CELO= + +# RPC URLs for different networks +VITE_APP_ARBITRUM_SEPOLIA_RPC= +VITE_APP_CELO_ALFAJORES_RPC= \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..90668f2 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,30 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'plugin:react/recommended', + 'plugin:tailwindcss/recommended', + 'prettier', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + 'react/prop-types': 'off', + 'react/react-in-jsx-scope': 'off', + 'tailwindcss/classnames-order': 'warn', + 'tailwindcss/enforces-shorthand': 'off', + }, + settings: { + react: { + version: 'detect', + }, + }, +}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index cfc8373..ffeb6a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ +// ... existing code ... +# misc +.DS_Store +.env +npm-debug.log +.trae/ + # See http://help.github.com/ignore-files/ for more about ignoring files. # dependencies @@ -12,4 +19,5 @@ # misc .DS_Store .env -npm-debug.log \ No newline at end of file +npm-debug.log +.trae/ \ No newline at end of file diff --git a/docker-compose.ci-demo-01.yml b/docker-compose.ci-demo-01.yml index c3b4f19..85dc77c 100644 --- a/docker-compose.ci-demo-01.yml +++ b/docker-compose.ci-demo-01.yml @@ -16,18 +16,10 @@ services: build: context: . dockerfile: Dockerfile - args: - - VITE_APP_BASE_URL=https://beta-api.collabberry.xyz - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS=0x86207Ce1202766041F414C47134A8b0A1607d899 - - VITE_NODE_ENV=production - - VITE_APP_URL=https://beta.collabberry.xyz - - VITE_APP_NETWORK=Arbitrum - - VITE_APP_BLOCK_EXPLORER=https://arbiscan.io/tx - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS_CELO=0x0e414560fdEeC039c4636b9392176ddc938b182D - - + env_file: + - .env image: collabberry/frontend-app-demo-01:latest networks: collabberry-frontend: - driver: bridge + driver: bridge \ No newline at end of file diff --git a/docker-compose.ci-dev.yml b/docker-compose.ci-dev.yml index 8c6a9e5..14496db 100644 --- a/docker-compose.ci-dev.yml +++ b/docker-compose.ci-dev.yml @@ -16,16 +16,10 @@ services: build: context: . dockerfile: Dockerfile - args: - - VITE_APP_BASE_URL=https://api.collabberry.xyz - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS=0x69a99AeAc1F2410e82A84E08268b336116Ab3B5a - - VITE_NODE_ENV=development - - VITE_APP_URL=https://app.collabberry.xyz - - VITE_APP_NETWORK=Arbitrum Sepolia - - VITE_APP_BLOCK_EXPLORER=https://sepolia.arbiscan.io/tx - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS_CELO=0x0e414560fdEeC039c4636b9392176ddc938b182D + env_file: + - .env image: collabberry/frontend-app:latest networks: collabberry-frontend: - driver: bridge + driver: bridge \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e763082..8fa5ee9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,17 +16,11 @@ services: build: context: . dockerfile: Dockerfile - args: - - VITE_APP_BASE_URL=http://16.171.142.20 - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS=0x69a99AeAc1F2410e82A84E08268b336116Ab3B5a - - VITE_NODE_ENV=development - - VITE_APP_URL=https://app.collabberry.xyz - - VITE_APP_NETWORK=Arbitrum Sepolia - - VITE_APP_BLOCK_EXPLORER=https://sepolia.arbiscan.io/tx - - VITE_APP_TEAM_POINTS_FACTORY_ADDRESS_CELO=0x0e414560fdEeC039c4636b9392176ddc938b182D + env_file: + - .env ports: - 4100:4100 networks: collabberry-frontend: - driver: bridge + driver: bridge \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ce2e149..18b42f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", + "eslint-plugin-tailwindcss": "^3.18.2", "postcss": "^8.4.21", "postcss-cli": "^11.0.0", "postcss-nesting": "^12.0.2", @@ -12058,6 +12059,23 @@ "semver": "bin/semver.js" } }, + "node_modules/eslint-plugin-tailwindcss": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.18.2.tgz", + "integrity": "sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "tailwindcss": "^3.4.0" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", @@ -32823,6 +32841,16 @@ "dev": true, "requires": {} }, + "eslint-plugin-tailwindcss": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.18.2.tgz", + "integrity": "sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==", + "dev": true, + "requires": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + } + }, "eslint-visitor-keys": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", diff --git a/package.json b/package.json index 59c423d..7bf8ae5 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.1", + "eslint-plugin-tailwindcss": "^3.18.2", "postcss": "^8.4.21", "postcss-cli": "^11.0.0", "postcss-nesting": "^12.0.2", diff --git a/src/@types/auth.ts b/src/@types/auth.ts index 8d67d64..64a2e02 100644 --- a/src/@types/auth.ts +++ b/src/@types/auth.ts @@ -35,17 +35,23 @@ export type RegisterCredential = { }; export type OrganizationData = { - id?: string; - par?: number; - compensationStartDay?: string; - assessmentStartDelayInDays?: number; - assessmentDurationInDays?: number - compensationPeriod?: number; - name: string; - logo?: string; -}; + id?: string + par?: number + compensationStartDay?: string + assessmentStartDelayInDays?: number + assessmentDurationInDays?: number + compensationPeriod?: number + name: string + logo?: string + chain?: 'arbitrum' | 'sepolia' + safeAddress?: string + stablecoinAddress?: string + recognitionTokenAddress?: string + recognitionMode?: 'hours-based' | 'discretionary' + chainId?: number +} -export type Assessment = { +export type User = { contributorId: string; cultureScore: number; workScore: number; @@ -59,4 +65,4 @@ export type ForgotPassword = { export type ResetPassword = { password: string; -}; +}; \ No newline at end of file diff --git a/src/@types/payouts.ts b/src/@types/payouts.ts new file mode 100644 index 0000000..d488fa0 --- /dev/null +++ b/src/@types/payouts.ts @@ -0,0 +1 @@ +export type RecognitionMode = 'hours-based' | 'discretionary'; \ No newline at end of file diff --git a/src/api/environment.ts b/src/api/environment.ts index 77af55a..f71d137 100644 --- a/src/api/environment.ts +++ b/src/api/environment.ts @@ -10,6 +10,10 @@ export const envVariables = { apiUrl: "VITE_APP_BASE_URL", teamPointsFactoryAddress: "VITE_APP_TEAM_POINTS_FACTORY_ADDRESS", teamPointsFactoryAddressCelo: "VITE_APP_TEAM_POINTS_FACTORY_ADDRESS_CELO", + rainbowProjectId: "VITE_APP_RAINBOW_PROJECT_ID", + walletConnectProjectId: "VITE_APP_WALLETCONNECT_PROJECT_ID", + arbitrumSepoliaRpc: "VITE_APP_ARBITRUM_SEPOLIA_RPC", + celoAlfajoresRpc: "VITE_APP_CELO_ALFAJORES_RPC", appUrl: "VITE_APP_URL", network: "VITE_APP_NETWORK", blockExplorer: "VITE_APP_BLOCK_EXPLORER" @@ -17,4 +21,4 @@ export const envVariables = { }; export const environment: typeof envVariables = - envionmentGenerator(envVariables); + envionmentGenerator(envVariables); \ No newline at end of file diff --git a/src/components/collabberry/PrivacyPolicy.tsx b/src/components/collabberry/PrivacyPolicy.tsx index e9a4ca1..c681844 100644 --- a/src/components/collabberry/PrivacyPolicy.tsx +++ b/src/components/collabberry/PrivacyPolicy.tsx @@ -3,7 +3,7 @@ import React from "react"; const PrivacyPolicy: React.FC = () => { return ( <> -
Privacy Policy
+
Privacy Policy

Your privacy is important to us. It is Collabberry's policy to respect @@ -11,7 +11,7 @@ const PrivacyPolicy: React.FC = () => { our website, http://collabberry.com, and other sites we own and operate.

-

+

1. Information we collect

@@ -19,7 +19,7 @@ const PrivacyPolicy: React.FC = () => { for example, to provide our services, to communicate with you, or to make our services better.

-

+

2. How we use information

@@ -32,18 +32,18 @@ const PrivacyPolicy: React.FC = () => { information relating to the website, and for marketing and promotional purposes.

-

3. Sharing information

+

3. Sharing information

We do not share your personal information with anyone except to comply with the law, develop our products, or protect our rights.

-

4. Security

+

4. Security

We take reasonable measures to help protect your personal information from loss, theft, misuse, and unauthorized access, disclosure, alteration, and destruction.

-

5. Contact us

+

5. Contact us

If you have any questions about this Privacy Policy, please contact us at support@collabberry.com. diff --git a/src/components/collabberry/TermsAndConditions.tsx b/src/components/collabberry/TermsAndConditions.tsx index af79b38..d468548 100644 --- a/src/components/collabberry/TermsAndConditions.tsx +++ b/src/components/collabberry/TermsAndConditions.tsx @@ -3,24 +3,24 @@ import React from "react"; const TermsAndConditions: React.FC = () => { return ( <> -

Terms and Conditions
+
Terms and Conditions

Welcome to Collabberry! These terms and conditions outline the rules and regulations for the use of Collabberry's Website.

-

Introduction

+

Introduction

By accessing this website we assume you accept these terms and conditions. Do not continue to use Collabberry if you do not agree to take all of the terms and conditions stated on this page.

-

Cookies

+

Cookies

We employ the use of cookies. By accessing Collabberry, you agreed to use cookies in agreement with the Collabberry's Privacy Policy.

-

License

+

License

Unless otherwise stated, Collabberry and/or its licensors own the intellectual property rights for all material on Collabberry. All @@ -28,7 +28,7 @@ const TermsAndConditions: React.FC = () => { Collabberry for your own personal use subjected to restrictions set in these terms and conditions.

-
diff --git a/src/components/collabberry/custom-components/CustomFields/RoundStatusTag.tsx b/src/components/collabberry/custom-components/CustomFields/RoundStatusTag.tsx index 5823ba7..32eb16b 100644 --- a/src/components/collabberry/custom-components/CustomFields/RoundStatusTag.tsx +++ b/src/components/collabberry/custom-components/CustomFields/RoundStatusTag.tsx @@ -22,7 +22,7 @@ const RoundStatusTag: React.FC = ({ roundStatus }) => { const status = getStatusTextAndColor(roundStatus); return ( - + {status.text} ); diff --git a/src/components/collabberry/custom-components/CustomRainbowKit/CustomAvatar.tsx b/src/components/collabberry/custom-components/CustomRainbowKit/CustomAvatar.tsx index e1f2bff..ca9e554 100644 --- a/src/components/collabberry/custom-components/CustomRainbowKit/CustomAvatar.tsx +++ b/src/components/collabberry/custom-components/CustomRainbowKit/CustomAvatar.tsx @@ -11,7 +11,7 @@ const CustomAvatar: any = () => { return ( ) }; diff --git a/src/components/collabberry/custom-components/CustomRainbowKit/CustomConnectButton.tsx b/src/components/collabberry/custom-components/CustomRainbowKit/CustomConnectButton.tsx index 82c159e..59d1a20 100644 --- a/src/components/collabberry/custom-components/CustomRainbowKit/CustomConnectButton.tsx +++ b/src/components/collabberry/custom-components/CustomRainbowKit/CustomConnectButton.tsx @@ -49,9 +49,9 @@ export const CustomConnectButton: React.FC = ({ ); diff --git a/src/components/collabberry/custom-components/CustomRainbowKit/CustomWrongNetworkButton.tsx b/src/components/collabberry/custom-components/CustomRainbowKit/CustomWrongNetworkButton.tsx index f0ca7f2..b286ff4 100644 --- a/src/components/collabberry/custom-components/CustomRainbowKit/CustomWrongNetworkButton.tsx +++ b/src/components/collabberry/custom-components/CustomRainbowKit/CustomWrongNetworkButton.tsx @@ -42,7 +42,7 @@ export const CustomWrongNetworkButton: React.FC = ({ if (connected && chain && chain.unsupported) { return ( - ); diff --git a/src/components/collabberry/custom-components/CustomTables/CustomSelectTable.tsx b/src/components/collabberry/custom-components/CustomTables/CustomSelectTable.tsx index 1bde1f5..daac148 100644 --- a/src/components/collabberry/custom-components/CustomTables/CustomSelectTable.tsx +++ b/src/components/collabberry/custom-components/CustomTables/CustomSelectTable.tsx @@ -90,7 +90,7 @@ const CustomSelectTable = ({ // // Assessed ✅ // - + Assessed ) : null; @@ -207,7 +207,7 @@ const CustomSelectTable = ({ {shouldShowPagination && ( -
+
({
)} -
+
+ + + ); +}; + +export default SettingsForm; \ No newline at end of file diff --git a/src/components/shared/CalendarView.tsx b/src/components/shared/CalendarView.tsx index c74c106..66cea47 100644 --- a/src/components/shared/CalendarView.tsx +++ b/src/components/shared/CalendarView.tsx @@ -169,7 +169,7 @@ const CalendarView = (props: CalendarViewProps) => { {!(isEnd && !isStart) && ( {arg.timeText} )} - + {arg.event.title}
diff --git a/src/components/shared/ConfirmDialog.tsx b/src/components/shared/ConfirmDialog.tsx index e28286c..5ca6d8a 100644 --- a/src/components/shared/ConfirmDialog.tsx +++ b/src/components/shared/ConfirmDialog.tsx @@ -49,7 +49,7 @@ const StatusIcon = ({ status }: { status: StatusType }) => { case 'warning': return ( @@ -60,7 +60,7 @@ const StatusIcon = ({ status }: { status: StatusType }) => { case 'danger': return ( @@ -97,7 +97,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => { return ( -
+
@@ -106,7 +106,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => { {children}
-
+
-
+
{searchResult.map((result) => (
{result.title}
diff --git a/src/components/template/StackedSideNav/StackedSideNavMini.tsx b/src/components/template/StackedSideNav/StackedSideNavMini.tsx index 1748f43..4047591 100644 --- a/src/components/template/StackedSideNav/StackedSideNavMini.tsx +++ b/src/components/template/StackedSideNav/StackedSideNavMini.tsx @@ -134,7 +134,7 @@ const StackedSideNavMini = (props: StackedSideNavMiniProps) => { ) : ( handleLinkMenuItemSelect({ key: nav.key, diff --git a/src/components/template/ThemeConfigurator/LayoutSwitcher.tsx b/src/components/template/ThemeConfigurator/LayoutSwitcher.tsx index 21a473d..35cad6b 100644 --- a/src/components/template/ThemeConfigurator/LayoutSwitcher.tsx +++ b/src/components/template/ThemeConfigurator/LayoutSwitcher.tsx @@ -71,7 +71,7 @@ const LayoutSwitcher = () => { value={[type]} onChange={(val) => onLayoutSelect(val[0] as LayoutType)} > -
+
{layouts.map((layout) => ( {({ active, onSegmentItemClick, disabled }) => { diff --git a/src/components/template/ThemeConfigurator/ThemeConfigurator.tsx b/src/components/template/ThemeConfigurator/ThemeConfigurator.tsx index ef295e1..bed7b85 100644 --- a/src/components/template/ThemeConfigurator/ThemeConfigurator.tsx +++ b/src/components/template/ThemeConfigurator/ThemeConfigurator.tsx @@ -11,8 +11,8 @@ export type ThemeConfiguratorProps = { const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => { return ( -
-
+
+
Dark Mode
diff --git a/src/components/template/ThemeConfigurator/ThemeSwitcher.tsx b/src/components/template/ThemeConfigurator/ThemeSwitcher.tsx index 08c0bf1..db8c627 100644 --- a/src/components/template/ThemeConfigurator/ThemeSwitcher.tsx +++ b/src/components/template/ThemeConfigurator/ThemeSwitcher.tsx @@ -91,7 +91,7 @@ const CustomSelectOption = ({ {label}
- {isSelected && } + {isSelected && }
) } diff --git a/src/components/template/UserDropdown.tsx b/src/components/template/UserDropdown.tsx index 7b55c6c..0210208 100644 --- a/src/components/template/UserDropdown.tsx +++ b/src/components/template/UserDropdown.tsx @@ -37,7 +37,7 @@ const _UserDropdown = ({ className }: CommonProps) => { placement="bottom-end" > -
+
} />
@@ -58,7 +58,7 @@ const _UserDropdown = ({ className }: CommonProps) => { className="flex h-full w-full px-2" to={item.path} > - + {item.icon} diff --git a/src/components/template/VerticalMenuContent/VerticalCollapsedMenuItem.tsx b/src/components/template/VerticalMenuContent/VerticalCollapsedMenuItem.tsx index 1ddb973..eb42b99 100644 --- a/src/components/template/VerticalMenuContent/VerticalCollapsedMenuItem.tsx +++ b/src/components/template/VerticalMenuContent/VerticalCollapsedMenuItem.tsx @@ -53,7 +53,7 @@ const DefaultItem = ({ nav, onLinkClick, userAuthority }: DefaultItemProps) => { {subNav.path ? ( onLinkClick?.({ @@ -117,7 +117,7 @@ const CollapsedItem = ({ {subNav.path ? ( onLinkClick?.({ diff --git a/src/components/template/VerticalMenuContent/VerticalSingleMenuItem.tsx b/src/components/template/VerticalMenuContent/VerticalSingleMenuItem.tsx index 4b50d39..2c1ecc9 100644 --- a/src/components/template/VerticalMenuContent/VerticalSingleMenuItem.tsx +++ b/src/components/template/VerticalMenuContent/VerticalSingleMenuItem.tsx @@ -51,7 +51,7 @@ const DefaultItem = (props: DefaultItemProps) => { onLinkClick?.({ key: nav.key, diff --git a/src/components/ui/Alert/Alert.tsx b/src/components/ui/Alert/Alert.tsx index e0f46a5..3632d35 100644 --- a/src/components/ui/Alert/Alert.tsx +++ b/src/components/ui/Alert/Alert.tsx @@ -171,7 +171,7 @@ const Alert = forwardRef((props, ref) => {
{title ? (
{title}
diff --git a/src/components/ui/DatePicker/DateTimepicker.tsx b/src/components/ui/DatePicker/DateTimepicker.tsx index dde4550..3ea2852 100644 --- a/src/components/ui/DatePicker/DateTimepicker.tsx +++ b/src/components/ui/DatePicker/DateTimepicker.tsx @@ -302,7 +302,7 @@ const DateTimepicker = forwardRef( onMonthChange={setCalendarMonth} onChange={handleValueChange} /> -
+
((props, ref) => { {label} {extraTooltip && ( - + )} @@ -119,7 +119,7 @@ const FormItem = forwardRef((props, ref) => {
diff --git a/src/components/ui/Menu/MenuCollapse.tsx b/src/components/ui/Menu/MenuCollapse.tsx index 58725f4..33f8e99 100644 --- a/src/components/ui/Menu/MenuCollapse.tsx +++ b/src/components/ui/Menu/MenuCollapse.tsx @@ -64,7 +64,7 @@ const MenuCollapse = (props: MenuCollapseProps) => { > {label} {label} {isSelected && ( )}
diff --git a/src/components/ui/Steps/StepItemWithAvatar.tsx b/src/components/ui/Steps/StepItemWithAvatar.tsx index 137b714..76f1a85 100644 --- a/src/components/ui/Steps/StepItemWithAvatar.tsx +++ b/src/components/ui/Steps/StepItemWithAvatar.tsx @@ -49,7 +49,7 @@ const StepItemWithAvatar = forwardRef( {`Step {status === COMPLETE && ( diff --git a/src/configs/routes.config/routes.config.ts b/src/configs/routes.config/routes.config.ts index 7a47568..665917f 100644 --- a/src/configs/routes.config/routes.config.ts +++ b/src/configs/routes.config/routes.config.ts @@ -82,6 +82,12 @@ export const protectedRoutes = [ component: lazy(() => import("@/views/main/Settings/Settings")), authority: [], }, + { + key: "org.settings.admin", + path: "/settings/admin", + component: lazy(() => import("@/views/main/Settings/AdminSettings")), + authority: [], + }, { key: "org.rounds", path: "/rounds", @@ -100,5 +106,4 @@ export const protectedRoutes = [ component: lazy(() => import("@/views/main/Rounds/ScoreView")), authority: [], }, -]; - +]; \ No newline at end of file diff --git a/src/services/ContractsService.ts b/src/services/ContractsService.ts index 9fa4d64..cec27e0 100644 --- a/src/services/ContractsService.ts +++ b/src/services/ContractsService.ts @@ -193,10 +193,10 @@ const _updateConfig = async ( contractAddress: string, isTransferable: boolean, isOutsideTransferAllowed: boolean, - materialWeight: BigInt, - baseTimeWeight: BigInt, + materialWeight: bigint, + baseTimeWeight: bigint, enableTimeScaling: boolean, - maxTimeScaling: BigInt, + maxTimeScaling: bigint, ethersSigner: ethers.JsonRpcSigner | undefined ): Promise => { try { @@ -510,10 +510,10 @@ export const useContractService = () => { contractAddress: string, isTransferable: boolean, isOutsideTransferAllowed: boolean, - materialWeight: BigInt, - baseTimeWeight: BigInt, + materialWeight: bigint, + baseTimeWeight: bigint, enableTimeScaling: boolean, - maxTimeScaling: BigInt, + maxTimeScaling: bigint, ): Promise => { if (!ethersSigner || !address) { diff --git a/src/services/LoadAndDispatchService.ts b/src/services/LoadAndDispatchService.ts index 34d269a..2c54463 100644 --- a/src/services/LoadAndDispatchService.ts +++ b/src/services/LoadAndDispatchService.ts @@ -44,8 +44,8 @@ export const refreshCurrentRound = async (dispatch: Dispatch, handleError: export const refreshUser = async (dispatch: Dispatch, handleError: (error: any) => void) => { try { - let response: any = await apiGetUser(); - let user = response?.data || {}; + const response: any = await apiGetUser(); + const user = response?.data || {}; if (user) { dispatch( setUser({ diff --git a/src/services/OrgService.ts b/src/services/OrgService.ts index e6a2d33..4375099 100644 --- a/src/services/OrgService.ts +++ b/src/services/OrgService.ts @@ -149,6 +149,14 @@ export async function apiAddTxHashToRound(roundId: string, data: any) { }); } +export async function apiUpdateOrganizationSettings(data: Partial) { + return ApiService.fetchData({ + url: '/orgs/settings', + method: 'put', + data, + }) +} + export async function apiEditOrganization(data: OrganizationData) { const formData = new FormData(); Object.keys(data).forEach((key) => { @@ -161,4 +169,4 @@ export async function apiEditOrganization(data: OrganizationData) { data: formData, headers: { "Content-Type": "multipart/form-data" }, }); -} +} \ No newline at end of file diff --git a/src/services/ValidationService.ts b/src/services/ValidationService.ts new file mode 100644 index 0000000..2a8eb9e --- /dev/null +++ b/src/services/ValidationService.ts @@ -0,0 +1,17 @@ +import { ethers } from 'ethers' +import TeamPointsABI from '../abi/TeamPoints.json' + +const getProvider = (chainId: number) => { + if (chainId === 11155111) { + // Sepolia + return new ethers.JsonRpcProvider( + import.meta.env.VITE_APP_ARBITRUM_SEPOLIA_RPC + ) + } + if (chainId === 42161) { + // Arbitrum + return new ethers.JsonRpcProvider(import.meta.env.VITE_APP_ARBITRUM_RPC) + } + // Fallback to mainnet - though we should have a value for it + return new ethers.JsonRpcProvider(import.meta.env.VITE_APP_MAINNET_RPC) +} \ No newline at end of file diff --git a/src/store/slices/auth/userSlice.ts b/src/store/slices/auth/userSlice.ts index ac2ea24..5e56fc8 100644 --- a/src/store/slices/auth/userSlice.ts +++ b/src/store/slices/auth/userSlice.ts @@ -10,6 +10,7 @@ export type UserState = { authority?: string[]; isAdmin?: boolean; isContractAdmin?: boolean; + isMinter?: boolean; totalFiat?: string; organization?: any }; @@ -23,6 +24,7 @@ const initialState: UserState = { authority: [], isAdmin: false, isContractAdmin: false, + isMinter: false, totalFiat: '', organization: null }; @@ -52,6 +54,7 @@ const userSlice = createSlice({ state.id = action.payload?.id; state.isAdmin = action.payload?.isAdmin; state.isContractAdmin = action.payload?.isContractAdmin; + state.isMinter = action.payload?.isMinter; state.totalFiat = action.payload?.totalFiat; state.organization = action.payload?.organization; }, @@ -59,4 +62,4 @@ const userSlice = createSlice({ }); export const { setUser, resetUser } = userSlice.actions; -export default userSlice.reducer; +export default userSlice.reducer; \ No newline at end of file diff --git a/src/utils/hooks/useAuth.ts b/src/utils/hooks/useAuth.ts index 6be0f2a..5c77f34 100644 --- a/src/utils/hooks/useAuth.ts +++ b/src/utils/hooks/useAuth.ts @@ -58,8 +58,8 @@ function useAuth() { try { if (token) { dispatch(walletConnected(token)); - let response: any = await apiGetUser(); - let user = response?.data || {}; + const response: any = await apiGetUser(); + const user = response?.data || {}; let url = appConfig.authenticatedEntryPath; if (!user || !user.organization) { user.username = "Anonymous"; @@ -72,6 +72,7 @@ function useAuth() { dispatch(signInSuccess(token)); } + const isMinter = await checkIsAdmin(user.id); dispatch( setUser({ profilePicture: user?.profilePicture, @@ -81,6 +82,7 @@ function useAuth() { telegramHandle: user?.telegramHandle, id: user?.id, isAdmin: user?.isAdmin, + isMinter, totalFiat: user?.totalFiat, organization: user?.organization, }) @@ -187,4 +189,4 @@ function useAuth() { }; } -export default useAuth; +export default useAuth; \ No newline at end of file diff --git a/src/views/auth/Invite/Invite.tsx b/src/views/auth/Invite/Invite.tsx index 6ff5f0f..95429ad 100644 --- a/src/views/auth/Invite/Invite.tsx +++ b/src/views/auth/Invite/Invite.tsx @@ -15,11 +15,11 @@ const Invite = () => { if (invitationToken) { return ( ); } - return ; + return ; }; export default Invite; diff --git a/src/views/auth/SignIn/SignIn.tsx b/src/views/auth/SignIn/SignIn.tsx index 28fffc1..7259a8e 100644 --- a/src/views/auth/SignIn/SignIn.tsx +++ b/src/views/auth/SignIn/SignIn.tsx @@ -83,13 +83,13 @@ const SignIn = () => {

Welcome to the land of berries!

-
+
-
+
I agree to the
{ component={PasswordInput} /> -
+
Remember Me diff --git a/src/views/auth/SignUp/SignUp.tsx b/src/views/auth/SignUp/SignUp.tsx index 52ef369..4114ff8 100644 --- a/src/views/auth/SignUp/SignUp.tsx +++ b/src/views/auth/SignUp/SignUp.tsx @@ -187,8 +187,8 @@ const SignUp = () => { }, }); try { - let response: any = await apiGetUser(); - let user = response?.data || {}; + const response: any = await apiGetUser(); + const user = response?.data || {}; if (user) { dispatch( setUser({ @@ -273,8 +273,8 @@ const SignUp = () => { } try { - let response: any = await apiGetUser(); - let user = response?.data || {}; + const response: any = await apiGetUser(); + const user = response?.data || {}; if (user) { dispatch( setUser({ @@ -449,7 +449,7 @@ const SignUp = () => { case 0: return ( -

+

Create Your Admin Profile

@@ -482,7 +482,7 @@ const SignUp = () => { value={formik.values.step1.username} /> -
+
{ isOpen={dialogOpen} closable={false} > -
+
{txHash ? (
-

+

Yay! Your contract has been deployed.

-
+

See your transaction on {network}:

{shortenedTx} @@ -548,18 +548,18 @@ const SignUp = () => { )}
-
+

You will be redirected to the next step in just a few seconds...

) : ( <>
-

+

Please wait ⏳

-
+

We're working our magic on the blockchain...

@@ -576,7 +576,7 @@ const SignUp = () => {
-

+

Create Your Organization

{
-
+
{/* */}

By submitting this form, you’ll deploy a new Team Points contract on {network || 'an unsupported Network'}. This transaction will require gas fees.

@@ -620,7 +620,7 @@ const SignUp = () => { case 2: return ( -

+

Your Agreement with the Organization

{ value={formik.values.step3.commitment} /> -
+
Compensation -
+
Calculation Example
-
+
Total Compensation = Market Rate ($3,000.00) * Commitment (75%) = $2,250.00 @@ -697,13 +697,13 @@ const SignUp = () => {
} > -
- {" "} +
+ {" "}
-
-
+
+
{formik.values.step3?.commitment && !formik.errors.step3?.commitment && formik.values.step3?.marketRate && !formik.errors.step3?.marketRate @@ -714,7 +714,7 @@ const SignUp = () => { : "Please input commitment and market rate to calculate the total compensation."}
-
+
{
diff --git a/src/views/auth/SignUpInviteLink/SignUpWithInviteLink.tsx b/src/views/auth/SignUpInviteLink/SignUpWithInviteLink.tsx index a625393..004dd59 100644 --- a/src/views/auth/SignUpInviteLink/SignUpWithInviteLink.tsx +++ b/src/views/auth/SignUpInviteLink/SignUpWithInviteLink.tsx @@ -80,8 +80,8 @@ const SignUpWithInviteLink = () => { const response = await apiRegisterAccount(data); if (response?.data) { try { - let response: any = await apiGetUser(); - let user = response?.data || {}; + const response: any = await apiGetUser(); + const user = response?.data || {}; if (user) { if (user?.organization?.id) { try { @@ -145,7 +145,7 @@ const SignUpWithInviteLink = () => {
{invitationToken && (
@@ -166,7 +166,7 @@ const SignUpWithInviteLink = () => {
-

+

Create Your Profile

@@ -191,7 +191,7 @@ const SignUpWithInviteLink = () => { value={formik.values.username} /> -
+
{ - return
; + return
; }; export default SingleMenuView; diff --git a/src/views/main/AgreementAssistant/AgreementForm.tsx b/src/views/main/AgreementAssistant/AgreementForm.tsx index 9e28f83..7b932cf 100644 --- a/src/views/main/AgreementAssistant/AgreementForm.tsx +++ b/src/views/main/AgreementAssistant/AgreementForm.tsx @@ -64,8 +64,8 @@ export const AgreementForm: React.FC = ({ return ( {/* Role Summary */} -
-

Role Summary

+
+

Role Summary

Role: {suggestions.role}

@@ -179,7 +179,7 @@ export const AgreementForm: React.FC = ({ Create Agreement -
+
Having trouble? Make sure all fields are filled out correctly.
diff --git a/src/views/main/Assessment/Assess.tsx b/src/views/main/Assessment/Assess.tsx index 49f3611..b50ddae 100644 --- a/src/views/main/Assessment/Assess.tsx +++ b/src/views/main/Assessment/Assess.tsx @@ -223,8 +223,8 @@ const Assess = () => { return ( <> {reviewedMembers.length === teamMembers.length ? ( -
-
+
+
Great job! You have submitted assessments for all the selected team members. If you want to select more team members, you can go back to the assessment panel. @@ -242,12 +242,12 @@ const Assess = () => {
) : ( -
+
= 768} onChange={(index) => onStepChange(index)} - className="justify-start mr-4" + className="mr-4 justify-start" > {teamMembers.map((member, index) => ( { ))} -
+
-
+
Culture Impact
{
-
-
Feedback
+
+
Feedback
{`What do you think ${currentMember.username} did well in the past month, appreciate in them, and believe they should continue doing this way?`} @@ -317,7 +317,7 @@ const Assess = () => { {`What do you think ${currentMember.username} didn't do well in the past month or can work on to improve?`}
-
+
{
-
+
-
+
diff --git a/src/views/main/Rounds/MintStatusTag.tsx b/src/views/main/Rounds/MintStatusTag.tsx index 80cc5f3..c3d6a05 100644 --- a/src/views/main/Rounds/MintStatusTag.tsx +++ b/src/views/main/Rounds/MintStatusTag.tsx @@ -16,7 +16,7 @@ const MintStatusTag: React.FC = ({ minted }) => { const status = getStatusTextAndColor(minted); return ( - + {status.text} ); diff --git a/src/views/main/Rounds/RoundView.tsx b/src/views/main/Rounds/RoundView.tsx index 86c8867..69bd24b 100644 --- a/src/views/main/Rounds/RoundView.tsx +++ b/src/views/main/Rounds/RoundView.tsx @@ -242,7 +242,7 @@ const RoundView: React.FC = () => { {isCurrentUser ? (
{contributor.hasAssessed ? ( - + ) : ( <> )} @@ -250,7 +250,7 @@ const RoundView: React.FC = () => { ) : (
{contributor.hasAssessed ? ( - + ) : selectedRound?.id === currentRound?.id && !reminded ? (
-
+

Round {selectedRound?.roundNumber}

{selectedRound?.status === RoundStatus.Completed && ( @@ -428,7 +428,7 @@ const RoundView: React.FC = () => { target="_blank" rel="noopener noreferrer" > -

View Team Points Minted @@ -439,8 +439,8 @@ const RoundView: React.FC = () => {

) : null} {selectedRound?.status === RoundStatus.InProgress && contributors.length && (
- -
Time left
+ +
Time left
{ return (
-
+

Rounds

{ @@ -215,9 +215,9 @@ const Rounds: React.FC = () => { ) : ( <> -
+
-
+
There are no rounds available.
{/*
diff --git a/src/views/main/Rounds/ScoreView.tsx b/src/views/main/Rounds/ScoreView.tsx index 2d8642a..a5777dc 100644 --- a/src/views/main/Rounds/ScoreView.tsx +++ b/src/views/main/Rounds/ScoreView.tsx @@ -12,7 +12,7 @@ import BerryPartialRating from "@/components/collabberry/custom-components/Custo import { RoundStatus } from "@/components/collabberry/utils/collabberry-constants"; const NoScoreAvailable: React.FC = () => ( - N/A + N/A ); @@ -77,19 +77,19 @@ const ContributorScoreView: React.FC = () => { { selectedUser?.profilePicture ? ( ) : ( ) }
-
+
{selectedUser?.username}
@@ -97,8 +97,8 @@ const ContributorScoreView: React.FC = () => { Total Score: { selectedRound?.status === RoundStatus.Completed ? ( -
- +
+ {selectedUser?.totalScore.toFixed(1) || 0} @@ -116,8 +116,8 @@ const ContributorScoreView: React.FC = () => { { selectedRound?.status === RoundStatus.Completed ? ( -
- +
+ {selectedUser?.workScore.toFixed(1) || 0} @@ -134,8 +134,8 @@ const ContributorScoreView: React.FC = () => { { selectedRound?.status === RoundStatus.Completed ? ( -
- +
+ {selectedUser?.cultureScore.toFixed(1) || 0} diff --git a/src/views/main/Scores/MyScores.tsx b/src/views/main/Scores/MyScores.tsx index 94fcaa2..17d6159 100644 --- a/src/views/main/Scores/MyScores.tsx +++ b/src/views/main/Scores/MyScores.tsx @@ -56,14 +56,14 @@ const MyScores: React.FC = () => { Back to Results
-
+

Round {scores?.roundName}

{/*
My Results
*/}
{ assessments.length ? ( -
+
{ Back to Results
-
+

Round {selectedRound?.roundNumber}

My Submitted Assessments
diff --git a/src/views/main/Scores/ScoreDetailCard.tsx b/src/views/main/Scores/ScoreDetailCard.tsx index ed1c597..632accf 100644 --- a/src/views/main/Scores/ScoreDetailCard.tsx +++ b/src/views/main/Scores/ScoreDetailCard.tsx @@ -13,8 +13,8 @@ interface ScoreCardProps { export const ScoreCard: React.FC = ({ title, score }) => { return ( -
{title}
-
+
{title}
+

{score.toFixed(1)} @@ -46,8 +46,8 @@ export const ScoreDetailCard: React.FC = ({ onEdit, }) => { return ( - -
+ +
{/* {(workScore === 5 || cultureScore === 5) && ( @@ -66,7 +66,7 @@ export const ScoreDetailCard: React.FC = ({
-
+
= ({

Culture Impact

-
+

{cultureScore.toFixed(1)}

{cultureScore === 5 && ( @@ -89,7 +89,7 @@ export const ScoreDetailCard: React.FC = ({

Work Contribution

-
+

{workScore.toFixed(1)}

{workScore === 5 && ( @@ -104,13 +104,13 @@ export const ScoreDetailCard: React.FC = ({
-

Did well

+

Did well

{feedbackPositive || '-'}

-

Could Improve

+

Could Improve

{feedbackNegative || '-'}

diff --git a/src/views/main/Scores/Scores.tsx b/src/views/main/Scores/Scores.tsx index 4ca538e..d4bdc52 100644 --- a/src/views/main/Scores/Scores.tsx +++ b/src/views/main/Scores/Scores.tsx @@ -169,9 +169,9 @@ const Scores: React.FC = () => { ) : ( -
+
-
+
There are no rounds available.
{/*
diff --git a/src/views/main/Settings/AdminSettings.tsx b/src/views/main/Settings/AdminSettings.tsx new file mode 100644 index 0000000..7b5dc57 --- /dev/null +++ b/src/views/main/Settings/AdminSettings.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import SettingsForm from '@/components/settings/SettingsForm'; +import Container from '@/components/shared/Container'; + +/** + * Renders the admin settings view, providing a container for the SettingsForm. + */ +const AdminSettings = () => { + return ( + +

Admin Settings

+
+ +
+
+ ); +}; + +export default AdminSettings; \ No newline at end of file diff --git a/src/views/main/Settings/CompensationSettings.tsx b/src/views/main/Settings/CompensationSettings.tsx index 9c2fddd..5ad909c 100644 --- a/src/views/main/Settings/CompensationSettings.tsx +++ b/src/views/main/Settings/CompensationSettings.tsx @@ -165,7 +165,7 @@ const CompensationSettings: React.FC = () => { -
+
= () => { /> */}
-
+
= () => { />
-
+
= () => { )}
-
+
+ + + + )} + +
+ ) +} + +export default PayoutsForm \ No newline at end of file diff --git a/src/views/main/Settings/Settings.tsx b/src/views/main/Settings/Settings.tsx index 8602f00..4dd2219 100644 --- a/src/views/main/Settings/Settings.tsx +++ b/src/views/main/Settings/Settings.tsx @@ -1,36 +1,41 @@ -import { Tabs } from '@/components/ui'; -import TabContent from '@/components/ui/Tabs/TabContent'; -import TabList from '@/components/ui/Tabs/TabList'; -import TabNav from '@/components/ui/Tabs/TabNav'; -import React from 'react'; -import CompensationSettings from './CompensationSettings'; -import TeamPointsContractSettings from './TeamPointsContractSettings'; -import { useSelector } from 'react-redux'; -import { RootState } from '@/store'; +import Tabs from '@/components/ui/Tabs' +import CompensationSettings from './CompensationSettings' +import TeamPointsContractSettings from './TeamPointsContractSettings' +import Payouts from './Payouts' +import useAuth from '@/utils/hooks/useAuth' +import AdminSettings from './AdminSettings' -const Settings: React.FC = () => { - const { isAdmin } = useSelector((state: RootState) => state.auth.user); +const { TabNav, TabList, TabContent } = Tabs - return ( -
-

Settings

- +const Settings = () => { + const { user } = useAuth() - - Compensation - Team Points Contract - -
- - - - - - -
-
-
- ); -}; + return ( + + + Compensation + Team Points Contract + Payouts + {user.isMinter && Admin Settings} + +
+ +

Compensation Settings

+ +
+ +

Team Points Contract Settings

+ +
+ + + + + + +
+
+ ) +} -export default Settings; \ No newline at end of file +export default Settings \ No newline at end of file diff --git a/src/views/main/Settings/SettingsForm.tsx b/src/views/main/Settings/SettingsForm.tsx new file mode 100644 index 0000000..704d09f --- /dev/null +++ b/src/views/main/Settings/SettingsForm.tsx @@ -0,0 +1,99 @@ +import { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { apiUpdateOrganizationSettings } from '@/services/OrgService'; +import { RootState } from '@/store'; + +/** + * A form for updating organization settings, including the Gnosis Safe address and the recognition token address. + * It handles user input, performs basic client-side validation, and submits the data to the backend. + */ +const SettingsForm = () => { + const user = useSelector((state: RootState) => state.auth.user); + const [safeAddress, setSafeAddress] = useState(''); + const [tokenAddress, setTokenAddress] = useState(''); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + /** + * Handles the form submission event. + * It prevents the default form submission, validates the input fields, + * and calls the organization service to update the settings on the backend. + * @param {React.FormEvent} e - The form submission event. + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setSuccess(''); + + if (!safeAddress || !tokenAddress) { + setError('Please fill out both address fields.'); + return; + } + + const addressRegex = /^0x[a-fA-F0-9]{40}$/; + if (!addressRegex.test(safeAddress) || !addressRegex.test(tokenAddress)) { + setError('Please enter valid Ethereum addresses.'); + return; + } + + try { + if (user && user.organization) { + await apiUpdateOrganizationSettings({ + safeAddress, + recognitionTokenAddress: tokenAddress, + }); + setSuccess('Settings updated successfully!'); + } + } catch (err) { + setError('Failed to update settings.'); + console.error(err); + } + }; + + return ( +
+ {error &&
{error}
} + {success &&
{success}
} +
+ +
+ setSafeAddress(e.target.value)} + /> +
+
+
+ +
+ setTokenAddress(e.target.value)} + /> +
+
+ +
+ ); +}; + +export default SettingsForm; \ No newline at end of file diff --git a/src/views/main/Settings/StatisticCard.tsx b/src/views/main/Settings/StatisticCard.tsx index 5155791..d540b1f 100644 --- a/src/views/main/Settings/StatisticCard.tsx +++ b/src/views/main/Settings/StatisticCard.tsx @@ -6,7 +6,7 @@ const TextInfoBlock: React.FC<{ title: string; value: string }> = ({ }) => { return (
-

{value}

+

{value}

{title}
); @@ -18,8 +18,8 @@ export const StatisticCard: React.FC<{ title: string; value: string }> = ({ }) => { return ( -
{title}
-
+
{title}
+

{value} diff --git a/src/views/main/Settings/TeamPointsContractSettings.tsx b/src/views/main/Settings/TeamPointsContractSettings.tsx index 687b54a..3d1de0d 100644 --- a/src/views/main/Settings/TeamPointsContractSettings.tsx +++ b/src/views/main/Settings/TeamPointsContractSettings.tsx @@ -294,7 +294,7 @@ const TeamPointsContractSettings: React.FC = () => { -
+
{/* */}

This transaction will modify contract settings on chain and require gas fees.

@@ -302,7 +302,7 @@ const TeamPointsContractSettings: React.FC = () => { )} -
+
diff --git a/src/views/main/Team/AddAgreement.tsx b/src/views/main/Team/AddAgreement.tsx index a733767..afaa1a1 100644 --- a/src/views/main/Team/AddAgreement.tsx +++ b/src/views/main/Team/AddAgreement.tsx @@ -154,9 +154,9 @@ const AddAgreementForm: React.FC = ({ {contributor && ( <> -

+

{hasAgreement ? 'Edit ' : 'Add '} Agreement for {contributor?.username}

= ({ value={formik.values.commitment} /> -
+
Compensation -
+
Calculation Example
-
+
Total Compensation = Market Rate ($3,000.00) * Commitment (75%) = $2,250.00 @@ -229,13 +229,13 @@ const AddAgreementForm: React.FC = ({
} > -
- {" "} +
+ {" "}
-
-
+
+
{formik.values.commitment && !formik.errors.commitment && formik.values.marketRate && @@ -247,7 +247,7 @@ const AddAgreementForm: React.FC = ({ : "Please input commitment and market rate to calculate the total compensation."}
-
+
= ({
-
+
diff --git a/src/views/main/Team/AdminManagement.tsx b/src/views/main/Team/AdminManagement.tsx index 5c5feea..2a1d2c2 100644 --- a/src/views/main/Team/AdminManagement.tsx +++ b/src/views/main/Team/AdminManagement.tsx @@ -14,7 +14,7 @@ import { HiArrowSmLeft, HiPlus } from 'react-icons/hi'; import { useNavigate } from 'react-router-dom'; import AddAdminForm from './AddAdminForm'; import { FiTrash } from 'react-icons/fi'; -import { Contributor } from '@/models/Organization.model'; +import { Contributor } from "../../../models/organization.model"; import ConfirmationDialog from '@/components/collabberry/custom-components/ConfirmationDialog'; import { ethers } from 'ethers'; import { refreshUser } from '@/services/LoadAndDispatchService'; @@ -160,7 +160,7 @@ const AdminManagement: React.FC = () => { contributor profile picture diff --git a/src/views/main/Team/EditOrganization.tsx b/src/views/main/Team/EditOrganization.tsx index a413d26..5386f3e 100644 --- a/src/views/main/Team/EditOrganization.tsx +++ b/src/views/main/Team/EditOrganization.tsx @@ -79,7 +79,7 @@ const EditOrganizationForm: React.FC = ({ return ( <> -

+

Edit Organization Details

@@ -98,7 +98,7 @@ const EditOrganizationForm: React.FC = ({ />
-
+
{contribution.user && contribution.amount && ( -
+
For a material contribution of {`$${contribution.amount}`}, {contributorOptions.find(option => option.value === contribution.user)?.label.split(' - ')[0]} will receive {Number(contribution.amount) * materialWeight} Team Points.
@@ -279,7 +279,7 @@ const MaterialContribution: React.FC = () => { ); })}
-
+