From 6da9c92bca4126655b97d087cf6dbfc21131cae7 Mon Sep 17 00:00:00 2001 From: Patryk Date: Thu, 24 Apr 2025 23:47:41 +0200 Subject: [PATCH] init wishlist, zustand and tanstack --- ArtisanConnect/api/notices.jsx | 20 ++++ ArtisanConnect/app/_layout.jsx | 27 ++++-- ArtisanConnect/app/index.jsx | 22 +++-- ArtisanConnect/app/notice/[id].jsx | 63 +++++++++++++ ArtisanConnect/app/notices.jsx | 36 ++++++-- ArtisanConnect/app/wishlist.jsx | 29 ++++++ ArtisanConnect/components/NoticeCard.jsx | 75 +++++++++------ ArtisanConnect/package-lock.json | 113 ++++++++++++++++++++++- ArtisanConnect/package.json | 6 +- ArtisanConnect/store/wishlistStore.jsx | 15 +++ 10 files changed, 354 insertions(+), 52 deletions(-) create mode 100644 ArtisanConnect/api/notices.jsx create mode 100644 ArtisanConnect/app/notice/[id].jsx create mode 100644 ArtisanConnect/app/wishlist.jsx create mode 100644 ArtisanConnect/store/wishlistStore.jsx diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx new file mode 100644 index 0000000..1d198c2 --- /dev/null +++ b/ArtisanConnect/api/notices.jsx @@ -0,0 +1,20 @@ +const API_URL = "https://testowe.zikor.pl/api/v1/notices/"; + +export async function listNotices() { + const response = await fetch(`${API_URL}get/all`); + const data = await response.json(); + if (!response.ok) { + throw new Error("Error"); + } + return data; +} + +export async function getNoticeById(noticeId) { + const response = await fetch(`${API_URL}get/${noticeId}`); + + const data = await response.json(); + if (!response.ok) { + throw new Error("Error"); + } + return data; +} diff --git a/ArtisanConnect/app/_layout.jsx b/ArtisanConnect/app/_layout.jsx index d5b8464..95a93d8 100644 --- a/ArtisanConnect/app/_layout.jsx +++ b/ArtisanConnect/app/_layout.jsx @@ -1,11 +1,20 @@ -import {Tabs} from 'expo-router'; +import { Tabs, Stack } from "expo-router"; import "@/global.css"; -import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'; +import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -export default function RootLayout(){ - return ( - - - - ); -} \ No newline at end of file +const queryClient = new QueryClient(); +export default function RootLayout() { + return ( + + + + + + + + {/* */} + + + ); +} diff --git a/ArtisanConnect/app/index.jsx b/ArtisanConnect/app/index.jsx index 7286e99..7c7e92a 100644 --- a/ArtisanConnect/app/index.jsx +++ b/ArtisanConnect/app/index.jsx @@ -1,12 +1,22 @@ -import { View, Text } from 'react-native'; -import { Button, ButtonText } from '@/components/ui/button'; -export default function Home(){ +import { View, Text } from "react-native"; +import { Link } from "expo-router"; +import { Button, ButtonText } from "@/components/ui/button"; + +export default function Home() { return ( - Home + Home + + + + + + ); -} \ No newline at end of file +} diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx new file mode 100644 index 0000000..e9d7980 --- /dev/null +++ b/ArtisanConnect/app/notice/[id].jsx @@ -0,0 +1,63 @@ +import { Stack, useLocalSearchParams } from "expo-router"; +import { Box } from "@/components/ui/box"; +import { Button, ButtonText } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Heading } from "@/components/ui/heading"; +import { Image } from "@/components/ui/image"; +import { Text } from "@/components/ui/text"; +import { VStack } from "@/components/ui/vstack"; +import { Icon, FavouriteIcon } from "@/components/ui/icon"; +import { useQuery } from "@tanstack/react-query"; +import { getNoticeById } from "@/api/notices"; +import { ActivityIndicator } from "react-native"; + +export default function NoticeDetails() { + const { id } = useLocalSearchParams(); + + const { + data: notice, + isLoading, + error, + } = useQuery({ + queryKey: ["notices", id], + queryFn: () => getNoticeById(Number(id)), + }); + + if (isLoading) { + return ; + } + + if (error) { + return Błąd, spróbuj ponownie póżniej; + } + + return ( + + + image + + + + {notice.title} + + + + {notice.price}zł + + + + + + ); +} diff --git a/ArtisanConnect/app/notices.jsx b/ArtisanConnect/app/notices.jsx index 72766dd..f93fd89 100644 --- a/ArtisanConnect/app/notices.jsx +++ b/ArtisanConnect/app/notices.jsx @@ -1,8 +1,30 @@ -import { View, Text } from 'react-native'; -import NoticeCard from '@/components/NoticeCard'; +import { FlatList, Text, ActivityIndicator } from "react-native"; +import { listNotices } from "@/api/notices"; +import { useQuery } from "@tanstack/react-query"; +import { NoticeCard } from "@/components/NoticeCard"; -export default function Catalog() { - return ( - - ); -} \ No newline at end of file +export default function Notices() { + const { data, isLoading, error } = useQuery({ + queryKey: ["notices"], + queryFn: listNotices, + }); + + if (isLoading) { + return ; + } + + if (error) { + return Błąd, spróbuj ponownie póżniej; + } + + return ( + } + /> + ); +} diff --git a/ArtisanConnect/app/wishlist.jsx b/ArtisanConnect/app/wishlist.jsx new file mode 100644 index 0000000..c9894e5 --- /dev/null +++ b/ArtisanConnect/app/wishlist.jsx @@ -0,0 +1,29 @@ +import { useWishlist } from "@/store/wishlistStore"; +import { FlatList } from "react-native"; +import { NoticeCard } from "@/components/NoticeCard"; +import { Ionicons } from "@expo/vector-icons"; +import { Box } from "@/components/ui/box"; +import { Text } from "@/components/ui/text"; + +export default function Wishlist() { + const wishlistNotices = useWishlist((state) => state.wishlistNotices); + if (wishlistNotices.length === 0) { + return ( + + + Brak ulubionych ogłoszeń + + ); + } + return ( + } + /> + ); +} diff --git a/ArtisanConnect/components/NoticeCard.jsx b/ArtisanConnect/components/NoticeCard.jsx index fdc164f..0afdaaa 100644 --- a/ArtisanConnect/components/NoticeCard.jsx +++ b/ArtisanConnect/components/NoticeCard.jsx @@ -1,43 +1,62 @@ import { Box } from "@/components/ui/box"; -import { Button, ButtonText } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Heading } from "@/components/ui/heading"; import { Image } from "@/components/ui/image"; import { Text } from "@/components/ui/text"; import { VStack } from "@/components/ui/vstack"; -import { Icon, FavouriteIcon } from "@/components/ui/icon"; +import { Link } from "expo-router"; +import { Pressable } from "react-native"; +import { useWishlist } from "@/store/wishlistStore"; +import { Ionicons } from "@expo/vector-icons"; -export default function NoticeCard() { - return ( - +export function NoticeCard({ notice }) { + const addNoticeToWishlist = useWishlist((state) => state.addNoticeToWishlist); + const removeNoticeFromWishlist = useWishlist( + (state) => state.removeNoticeFromWishlist + ); + const isInWishlist = useWishlist((state) => + state.wishlistNotices.some((item) => item.noticeId == notice.noticeId) + ); + + return ( + + + image - - Fashion Clothing - - - - Cotton Kurta - + + + {notice.title} + + + + {notice.price}zł + + { + if (isInWishlist) { + removeNoticeFromWishlist(notice.noticeId); // Usuń z ulubionych + } else { + addNoticeToWishlist(notice); // Dodaj do ulubionych + } + }} + > + + + - - - - - ); + + + ); } - diff --git a/ArtisanConnect/package-lock.json b/ArtisanConnect/package-lock.json index 7f24a4a..08d2b2a 100644 --- a/ArtisanConnect/package-lock.json +++ b/ArtisanConnect/package-lock.json @@ -9,12 +9,15 @@ "version": "1.0.0", "dependencies": { "@expo/html-elements": "^0.4.2", + "@expo/vector-icons": "^14.1.0", "@gluestack-ui/button": "^1.0.14", "@gluestack-ui/icon": "^0.1.27", "@gluestack-ui/image": "^0.1.17", "@gluestack-ui/nativewind-utils": "^1.0.26", "@gluestack-ui/overlay": "^0.1.22", "@gluestack-ui/toast": "^1.0.9", + "@tanstack/react-query": "^5.74.4", + "axios": "^1.8.4", "babel-plugin-module-resolver": "^5.0.2", "expo": "~52.0.46", "expo-constants": "~17.0.8", @@ -31,7 +34,8 @@ "react-native-screens": "~4.4.0", "react-native-svg": "^15.2.0", "react-native-web": "~0.19.13", - "tailwindcss": "^3.4.17" + "tailwindcss": "^3.4.17", + "zustand": "^5.0.3" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -3812,6 +3816,32 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.74.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.4.tgz", + "integrity": "sha512-YuG0A0+3i9b2Gfo9fkmNnkUWh5+5cFhWBN0pJAHkHilTx6A0nv8kepkk4T4GRt4e5ahbtFj2eTtkiPcVU1xO4A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.74.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.4.tgz", + "integrity": "sha512-mAbxw60d4ffQ4qmRYfkO1xzRBPUEf/72Dgo3qqea0J66nIKuDTLEqQt0ku++SDFlMGMnB6uKDnEG1xD/TDse4Q==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.74.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4253,6 +4283,32 @@ "node": ">= 4.0.0" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", @@ -6534,6 +6590,26 @@ "node": ">=0.4.0" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -9988,6 +10064,12 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -12698,6 +12780,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json index 5d75645..d4bd6e4 100644 --- a/ArtisanConnect/package.json +++ b/ArtisanConnect/package.json @@ -10,12 +10,15 @@ }, "dependencies": { "@expo/html-elements": "^0.4.2", + "@expo/vector-icons": "^14.1.0", "@gluestack-ui/button": "^1.0.14", "@gluestack-ui/icon": "^0.1.27", "@gluestack-ui/image": "^0.1.17", "@gluestack-ui/nativewind-utils": "^1.0.26", "@gluestack-ui/overlay": "^0.1.22", "@gluestack-ui/toast": "^1.0.9", + "@tanstack/react-query": "^5.74.4", + "axios": "^1.8.4", "babel-plugin-module-resolver": "^5.0.2", "expo": "~52.0.46", "expo-constants": "~17.0.8", @@ -32,7 +35,8 @@ "react-native-screens": "~4.4.0", "react-native-svg": "^15.2.0", "react-native-web": "~0.19.13", - "tailwindcss": "^3.4.17" + "tailwindcss": "^3.4.17", + "zustand": "^5.0.3" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/ArtisanConnect/store/wishlistStore.jsx b/ArtisanConnect/store/wishlistStore.jsx new file mode 100644 index 0000000..926d4c7 --- /dev/null +++ b/ArtisanConnect/store/wishlistStore.jsx @@ -0,0 +1,15 @@ +import { create } from "zustand"; + +export const useWishlist = create((set) => ({ + wishlistNotices: [], + addNoticeToWishlist: (wishlistNotice) => + set((state) => ({ + wishlistNotices: [...state.wishlistNotices, wishlistNotice], + })), + removeNoticeFromWishlist: (noticeId) => + set((state) => ({ + wishlistNotices: state.wishlistNotices.filter( + (item) => item.noticeId != noticeId + ), + })), +}));