diff --git a/package-lock.json b/package-lock.json index b146856c..6208e36e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18534,6 +18534,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/src/components/MobileNavButton.tsx b/src/components/MobileNavButton.tsx new file mode 100644 index 00000000..f8502bf4 --- /dev/null +++ b/src/components/MobileNavButton.tsx @@ -0,0 +1,35 @@ +import Link from 'next/link'; +import React from 'react'; + +interface MobileNavButtonProps { + href: string; + icon: React.ReactNode; + label: string; + active?: boolean; +} + +export function MobileNavButton({ + href, + icon, + label, + active = false, +}: MobileNavButtonProps) { + return ( + + {icon} + + ); +} \ No newline at end of file diff --git a/src/components/analytics/AnalyticsTableShimmer.tsx b/src/components/analytics/AnalyticsTableShimmer.tsx new file mode 100644 index 00000000..fc948fa9 --- /dev/null +++ b/src/components/analytics/AnalyticsTableShimmer.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +interface AnalyticsTableShimmerProps { + rows?: number; +} + +export function AnalyticsTableShimmer({ + rows = 6, +}: AnalyticsTableShimmerProps) { + return ( +
+
+ {Array.from({ length: rows }).map((_, index) => ( +
+
+
+
+
+
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/src/components/eslint/deep-import-rules.js b/src/components/eslint/deep-import-rules.js new file mode 100644 index 00000000..39f9a01a --- /dev/null +++ b/src/components/eslint/deep-import-rules.js @@ -0,0 +1,34 @@ +/** + * Enforce deep imports across components. + * + * Prevents importing from barrel files such as: + * import { Button } from "@/components"; + * + * Encourages: + * import Button from "@/components/ui/Button"; + */ + +export const deepImportRules = { + "no-restricted-imports": [ + "warn", + { + paths: [ + { + name: "@/components", + message: + "Use deep imports instead of barrel imports for better code-splitting.", + }, + ], + patterns: [ + { + group: [ + "@/components/index", + "@/components/**/index", + ], + message: + "Import components directly from their source file instead of a barrel export.", + }, + ], + }, + ], +}; \ No newline at end of file diff --git a/src/hooks/usePropertyCardData.ts b/src/hooks/usePropertyCardData.ts new file mode 100644 index 00000000..e9345b40 --- /dev/null +++ b/src/hooks/usePropertyCardData.ts @@ -0,0 +1,63 @@ +import { useMemo } from 'react'; + +import type { Property } from '@/types/property'; +import { useComparisonStore } from '@/store/comparisonStore'; +import { useCompareStore } from '@/store/compareStore'; +import { useFavoritesStore } from '@/store/favoritesStore'; + +import { + formatPrice, + formatNumber, + formatROI, + getBlockchainColor, +} from '@/utils/searchUtils'; + +export function usePropertyCardData(property: Property) { + const { isPropertySelected } = useComparisonStore(); + + const selectedIds = useCompareStore((state) => state.selectedIds); + + const { isFavorite } = useFavoritesStore(); + + return useMemo(() => { + const isCompared = selectedIds.includes(property.id); + + return { + isSelectedForComparison: isPropertySelected(property.id), + + isCompared, + + compareLimitReached: + selectedIds.length >= 3 && !isCompared, + + isFavorite: isFavorite(property.id), + + formattedROI: formatROI(property.metrics.roi), + + formattedSquareFeet: formatNumber( + property.details.squareFeet, + ), + + formattedAvailableTokens: formatNumber( + property.tokenInfo.available, + ), + + formattedTotalSupply: formatNumber( + property.tokenInfo.totalSupply, + ), + + formattedTokenPrice: formatPrice( + property.price.perToken, + ), + + blockchainColor: getBlockchainColor( + property.blockchain, + ), + }; + }, [ + property, + selectedIds, + isPropertySelected, + isFavorite, + ]); +} \ No newline at end of file