diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 386164f..2078c1e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ "react": "^19.1.1", "react-dom": "^19.1.1", "react-hook-form": "^7.62.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.9.1", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.13", @@ -45,6 +46,7 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/react-icons": "^2.2.7", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", "@wagmi/core": "^2.20.3", @@ -6622,6 +6624,27 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-icon-base": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/react-icon-base/-/react-icon-base-2.1.6.tgz", + "integrity": "sha512-ebbN1JjCm6RxBd3HdI1+8VCdiOI4qMjnl9DIHWJFrB/eYLF4mzIgdL34PIqCJBLY3vlwil9v6IHQvzsa8vgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-icons": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-2.2.7.tgz", + "integrity": "sha512-qxc8xtwgDG5Ub/WILU9tZa7zxz2UZqOU4yXbBa+Xg+0LbP031NB9gvf1d/ALvHLGCsCf3WEVttNoW/wc30jn1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*", + "@types/react-icon-base": "*" + } + }, "node_modules/@types/secp256k1": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", @@ -15427,6 +15450,15 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7b20f8d..a28587c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "react-dom": "^19.1.1", "react-hook-form": "^7.62.0", "react-router-dom": "^7.9.1", + "react-icons": "^5.5.0", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.13", "viem": "^2.37.5", @@ -52,6 +53,7 @@ "@testing-library/jest-dom": "^6.8.0", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", + "@types/react-icons": "^2.2.7", "@typescript-eslint/eslint-plugin": "^8.43.0", "@typescript-eslint/parser": "^8.43.0", "@wagmi/core": "^2.20.3", diff --git a/frontend/src/components/badges/BadgesList.tsx b/frontend/src/components/badges/BadgesList.tsx index c1ca6b5..cd3a7cf 100644 --- a/frontend/src/components/badges/BadgesList.tsx +++ b/frontend/src/components/badges/BadgesList.tsx @@ -14,9 +14,11 @@ import type { Badge } from "@/lib/types/badges"; import { Search } from "lucide-react"; import { CreateBadgeButton } from "@/components/badges/CreateBadgeButton"; import { Input } from "../ui/input"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; +import ErrorDisplay from "@/components/displayError/index"; export function BadgesList(): React.ReactElement { - const { data, isLoading } = useGetBadges(); + const { data, isLoading, error } = useGetBadges(); const [searchQuery, setSearchQuery] = useState(""); const list = (data && data.length > 0 ? data : HARD_CODED_BADGES) as Badge[]; @@ -27,42 +29,53 @@ export function BadgesList(): React.ReactElement { }, [list, searchQuery]); if (isLoading) { - return
Loading badges…
; + return ( +
+ +
+ ); } + return ( -
-
-
- - setSearchQuery(e.target.value)} - /> -
+ <> + {error ? ( + + ) : ( +
+
+
+ + setSearchQuery(e.target.value)} + /> +
- -
+ +
-
- {filtered.map((badge) => ( - - - -
- - {badge.name} -
-
- {badge.description} -
- -
- ))} -
-
+
+ {filtered.map((badge) => ( + + + +
+ + {badge.name} +
+
+ {badge.description} +
+ +
+ ))} +
+ + )} + ); } diff --git a/frontend/src/components/displayError/index.tsx b/frontend/src/components/displayError/index.tsx new file mode 100644 index 0000000..e136d9f --- /dev/null +++ b/frontend/src/components/displayError/index.tsx @@ -0,0 +1,14 @@ +type ErrorDisplayProps = { + error: any; +}; + +export default function ErrorDisplay({ error }: ErrorDisplayProps) { + if (!error) return null; + + return ( +

+ ⚠️ + {"An error occurred, please try again later or contact support on discord"} +

+ ); +} \ No newline at end of file diff --git a/frontend/src/components/profiles/list/ProfilesList.tsx b/frontend/src/components/profiles/list/ProfilesList.tsx index febec81..30a96c0 100644 --- a/frontend/src/components/profiles/list/ProfilesList.tsx +++ b/frontend/src/components/profiles/list/ProfilesList.tsx @@ -7,6 +7,8 @@ import type { Profile } from "@/lib/types/profiles"; import { useMemo, useState } from "react"; import { useGetAttestations } from "@/hooks/attestations/use-get-attestations"; import { Input } from "../../ui/input"; +import { AiOutlineLoading3Quarters } from "react-icons/ai"; +import ErrorDisplay from "@/components/displayError/index"; export function ProfilesList() { const { data, isLoading, error } = useGetProfiles(); @@ -16,13 +18,13 @@ export function ProfilesList() { const baseProfiles: Profile[] = data && data.length > 0 ? data.map((p) => ({ - address: p.address, - name: p.name || "", - description: p.description || "", - githubLogin: p.github_login, - attestationCount: 0, - attestations: [], - })) + address: p.address, + name: p.name || "", + description: p.description || "", + githubLogin: p.github_login, + attestationCount: 0, + attestations: [], + })) : PROFILES; const attestationsByRecipient = useMemo(() => { @@ -63,46 +65,61 @@ export function ProfilesList() { return profiles.filter((p) => (p.name || "").toLowerCase().includes(q)); }, [profiles, searchQuery]); - return ( -
-
-
- - setSearchQuery(e.target.value)} - /> -
- -
- {isLoading || attestations.isLoading ? ( -

Loading profiles...

- ) : null} - {error || attestations.error ? ( -

- {(error as any)?.message ?? - (attestations.error as any)?.message ?? - "An error occurred"} -

- ) : null} -
- {filtered.map((profile) => ( - - ))} + if (isLoading || attestations.isLoading) { + return ( +
+
-
+ ); + } + + if (data && data.length === 0) { + return ( +

+ ⚠️ + {"No Profile Found"} +

+ ); + } + + return ( + <> + {error ? ( + + ) : ( +
+
+
+ + setSearchQuery(e.target.value)} + /> +
+ + +
+ +
+ {filtered.map((profile) => ( + + ))} +
+
+ )} + ); } -export default ProfilesList; \ No newline at end of file +export default ProfilesList;