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 (
+
+
+
+
+
+
+ {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 (
+
+
+
-
- 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
+ ),
+ })),
+}));