diff --git a/frontend/package.json b/frontend/package.json index 91745be..90ba871 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,9 @@ "preview": "vite preview" }, "dependencies": { + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", + "@tanstack/react-query": "^5.37.1", "@tanstack/react-router": "^1.28.7", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index ccd15c1..4e4cf47 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -5,6 +5,15 @@ settings: excludeLinksFromLockfile: false dependencies: + '@mdi/js': + specifier: ^7.4.47 + version: 7.4.47 + '@mdi/react': + specifier: ^1.6.1 + version: 1.6.1 + '@tanstack/react-query': + specifier: ^5.37.1 + version: 5.37.1(react@18.2.0) '@tanstack/react-router': specifier: ^1.28.7 version: 1.28.7(react-dom@18.2.0)(react@18.2.0) @@ -729,6 +738,16 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true + /@mdi/js@7.4.47: + resolution: {integrity: sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==} + dev: false + + /@mdi/react@1.6.1: + resolution: {integrity: sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==} + dependencies: + prop-types: 15.8.1 + dev: false + /@noble/curves@1.3.0: resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} dependencies: @@ -920,6 +939,19 @@ packages: engines: {node: '>=12'} dev: false + /@tanstack/query-core@5.36.1: + resolution: {integrity: sha512-BteWYEPUcucEu3NBcDAgKuI4U25R9aPrHSP6YSf2NvaD2pSlIQTdqOfLRsxH9WdRYg7k0Uom35Uacb6nvbIMJg==} + dev: false + + /@tanstack/react-query@5.37.1(react@18.2.0): + resolution: {integrity: sha512-EhtBNA8GL3XFeSx6VYUjXQ96n44xe3JGKZCzBINrCYlxbZP6UwBafv7ti4eSRWc2Fy+fybQre0w17gR6lMzULA==} + peerDependencies: + react: ^18.0.0 + dependencies: + '@tanstack/query-core': 5.36.1 + react: 18.2.0 + dev: false + /@tanstack/react-router@1.28.7(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-eSOg/ffG8KJ9E3oAJnhiiQtz5dNM/7M5PL2BItZeAZF+0CSIGi0eI97orGDknY42iMdE+1KRkCKdfuutln2dxw==} engines: {node: '>=12'} @@ -2275,7 +2307,6 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-hash@3.0.0: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} @@ -2458,6 +2489,14 @@ packages: hasBin: true dev: true + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2477,6 +2516,10 @@ packages: scheduler: 0.23.0 dev: false + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} diff --git a/frontend/src/components/CampaignsGrid.tsx b/frontend/src/components/CampaignsGrid.tsx new file mode 100644 index 0000000..e52deab --- /dev/null +++ b/frontend/src/components/CampaignsGrid.tsx @@ -0,0 +1,59 @@ +import { mdiAlertCircleOutline } from "@mdi/js"; +import Icon from "@mdi/react"; +import { useQuery } from "@tanstack/react-query"; +import { fetchCampaigns } from "../query/fetchers/campaigns"; +import { cn } from "../utils/style"; + +interface Props { + className?: string; +} + +export function CampaignsGrid({ className }: Props) { + const campaignsQuery = useQuery({ + queryKey: ["campaigns"], + queryFn: fetchCampaigns, + }); + + if (campaignsQuery.isPending) { + return ( +
+ +
+ ); + } + + if (campaignsQuery.isError) { + return ( +
+
+ + Couldn't load campaigns. +
+
+ ); + } + + return ( +
+ {campaignsQuery.data.map((campaign) => ( +
+
+ Shoes +
+
+

{campaign.name}

+ Lorem ipsum +
+
+ ))} +
+ ); +} diff --git a/frontend/src/config.ts b/frontend/src/config.ts new file mode 100644 index 0000000..e701c0b --- /dev/null +++ b/frontend/src/config.ts @@ -0,0 +1,3 @@ +export const CONFIG = { + API_BASE_URL: "http://localhost:7231", +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 5bddb1e..4434b11 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,3 +1,4 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import React from "react"; import ReactDOM from "react-dom/client"; @@ -5,6 +6,8 @@ import { ContextsProvider } from "./components/ContextsProvider"; import "./index.css"; import { routeTree } from "./routeTree.gen"; +const queryClient = new QueryClient(); + const router = createRouter({ routeTree }); declare module "@tanstack/react-router" { interface Register { @@ -15,7 +18,9 @@ declare module "@tanstack/react-router" { ReactDOM.createRoot(document.getElementById("root")!).render( - + + + ); diff --git a/frontend/src/query/fetchers/campaigns.ts b/frontend/src/query/fetchers/campaigns.ts new file mode 100644 index 0000000..cd187ed --- /dev/null +++ b/frontend/src/query/fetchers/campaigns.ts @@ -0,0 +1,15 @@ +import { CONFIG } from "../../config"; + +export const fetchCampaigns = async () => { + const resp = await fetch(`${CONFIG.API_BASE_URL}/campaigns`); + if (!resp.ok) { + throw new Error("Network error."); + } + + const respBody = await resp.json(); + if (!respBody.ok) { + throw new Error(respBody.message || "Something went wrong."); + } + + return respBody.data; +}; diff --git a/frontend/src/routes/index.lazy.tsx b/frontend/src/routes/index.lazy.tsx index 8fbb1bb..bc3604b 100644 --- a/frontend/src/routes/index.lazy.tsx +++ b/frontend/src/routes/index.lazy.tsx @@ -1,4 +1,5 @@ import { createLazyFileRoute } from "@tanstack/react-router"; +import { CampaignsGrid } from "../components/CampaignsGrid"; export const Route = createLazyFileRoute("/")({ component: IndexPage, @@ -14,12 +15,14 @@ function IndexPage() { -

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci nesciunt ipsa possimus sed? Maxime eos iusto facere natus commodi consectetur ad pariatur minima quisquam. Soluta esse minus porro nemo at.

+ + ); } diff --git a/frontend/src/utils/style.ts b/frontend/src/utils/style.ts new file mode 100644 index 0000000..3cb1de7 --- /dev/null +++ b/frontend/src/utils/style.ts @@ -0,0 +1,5 @@ +export const cn = ( + ...args: Array +): string => { + return args.filter((arg) => typeof arg === "string").join(" "); +};