add TanStack Query & add icons & list campaigns from backend

This commit is contained in:
osmannyildiz 2024-05-18 21:05:57 +03:00
parent de04100c19
commit f7c9a64214
8 changed files with 139 additions and 3 deletions

View File

@ -10,6 +10,9 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@tanstack/react-query": "^5.37.1",
"@tanstack/react-router": "^1.28.7", "@tanstack/react-router": "^1.28.7",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -5,6 +5,15 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
dependencies: 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': '@tanstack/react-router':
specifier: ^1.28.7 specifier: ^1.28.7
version: 1.28.7(react-dom@18.2.0)(react@18.2.0) version: 1.28.7(react-dom@18.2.0)(react@18.2.0)
@ -729,6 +738,16 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
dev: true 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: /@noble/curves@1.3.0:
resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==} resolution: {integrity: sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==}
dependencies: dependencies:
@ -920,6 +939,19 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: false 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): /@tanstack/react-router@1.28.7(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-eSOg/ffG8KJ9E3oAJnhiiQtz5dNM/7M5PL2BItZeAZF+0CSIGi0eI97orGDknY42iMdE+1KRkCKdfuutln2dxw==} resolution: {integrity: sha512-eSOg/ffG8KJ9E3oAJnhiiQtz5dNM/7M5PL2BItZeAZF+0CSIGi0eI97orGDknY42iMdE+1KRkCKdfuutln2dxw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -2275,7 +2307,6 @@ packages:
/object-assign@4.1.1: /object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/object-hash@3.0.0: /object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
@ -2458,6 +2489,14 @@ packages:
hasBin: true hasBin: true
dev: 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: /punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2477,6 +2516,10 @@ packages:
scheduler: 0.23.0 scheduler: 0.23.0
dev: false dev: false
/react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: false
/react-refresh@0.14.0: /react-refresh@0.14.0:
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}

View File

@ -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 (
<div className={className}>
<span className="loading loading-spinner loading-lg"></span>
</div>
);
}
if (campaignsQuery.isError) {
return (
<div className={cn("flex justify-center", className)}>
<div role="alert" className="alert alert-error w-auto">
<Icon path={mdiAlertCircleOutline} size={1} />
<span>Couldn't load campaigns.</span>
</div>
</div>
);
}
return (
<div
className={cn(
"grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4",
className
)}
>
{campaignsQuery.data.map((campaign) => (
<div key={campaign._id} className="card bg-base-200">
<figure>
<img
src="https://img.daisyui.com/images/stock/photo-1606107557195-0e29a4b5b4aa.jpg"
alt="Shoes"
/>
</figure>
<div className="card-body items-start">
<h2 className="text-xl font-bold">{campaign.name}</h2>
<span>Lorem ipsum</span>
</div>
</div>
))}
</div>
);
}

3
frontend/src/config.ts Normal file
View File

@ -0,0 +1,3 @@
export const CONFIG = {
API_BASE_URL: "http://localhost:7231",
};

View File

@ -1,3 +1,4 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router"; import { RouterProvider, createRouter } from "@tanstack/react-router";
import React from "react"; import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
@ -5,6 +6,8 @@ import { ContextsProvider } from "./components/ContextsProvider";
import "./index.css"; import "./index.css";
import { routeTree } from "./routeTree.gen"; import { routeTree } from "./routeTree.gen";
const queryClient = new QueryClient();
const router = createRouter({ routeTree }); const router = createRouter({ routeTree });
declare module "@tanstack/react-router" { declare module "@tanstack/react-router" {
interface Register { interface Register {
@ -15,7 +18,9 @@ declare module "@tanstack/react-router" {
ReactDOM.createRoot(document.getElementById("root")!).render( ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode> <React.StrictMode>
<ContextsProvider> <ContextsProvider>
<RouterProvider router={router} /> <QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</ContextsProvider> </ContextsProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@ -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;
};

View File

@ -1,4 +1,5 @@
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { CampaignsGrid } from "../components/CampaignsGrid";
export const Route = createLazyFileRoute("/")({ export const Route = createLazyFileRoute("/")({
component: IndexPage, component: IndexPage,
@ -14,12 +15,14 @@ function IndexPage() {
</h2> </h2>
</div> </div>
<p> <p className="mb-8">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci
nesciunt ipsa possimus sed? Maxime eos iusto facere natus commodi nesciunt ipsa possimus sed? Maxime eos iusto facere natus commodi
consectetur ad pariatur minima quisquam. Soluta esse minus porro nemo consectetur ad pariatur minima quisquam. Soluta esse minus porro nemo
at. at.
</p> </p>
<CampaignsGrid />
</div> </div>
); );
} }

View File

@ -0,0 +1,5 @@
export const cn = (
...args: Array<undefined | null | boolean | string>
): string => {
return args.filter((arg) => typeof arg === "string").join(" ");
};