From eb1ebc3464e26c2e9556f935e7e4761bd23a8075 Mon Sep 17 00:00:00 2001 From: Kuba Date: Sun, 25 May 2025 17:41:07 +0200 Subject: [PATCH 01/18] V1 --- ArtisanConnect/api/client.jsx | 13 +++++ ArtisanConnect/app/notice/[id].jsx | 77 ++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 ArtisanConnect/api/client.jsx diff --git a/ArtisanConnect/api/client.jsx b/ArtisanConnect/api/client.jsx new file mode 100644 index 0000000..6f0489c --- /dev/null +++ b/ArtisanConnect/api/client.jsx @@ -0,0 +1,13 @@ +import axios from "axios"; + +const API_URL = "https://testowe.zikor.pl/api/v1"; + +export async function getUserById(userId) { + try { + const response = await axios.get(`${API_URL}/clients/get/${userId}`); + return response.data; + } catch (err) { + console.error(`Nie udało się pobrać danych użytkownika o ID ${userId}.`, err.response.status); + throw err; + } +} diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index 5847f79..9da0bcd 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -11,6 +11,9 @@ import {useEffect, useState} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; import {Pressable} from "react-native"; +import {getUserById} from "@/api/client"; + + export default function NoticeDetails() { const {id} = useLocalSearchParams(); @@ -19,6 +22,9 @@ export default function NoticeDetails() { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); + const [user, setUser] = useState(null); + const [isUserLoading, setIsUserLoading] = useState(true); + const {getNoticeById, getAllImagesByNoticeId} = useNoticesStore(); const addNoticeToWishlist = useWishlist((state) => state.addNoticeToWishlist); @@ -69,6 +75,25 @@ export default function NoticeDetails() { } }, [notice]); + useEffect(() => { + const fetchUser = async () => { + if (notice && notice.clientId) { + setIsUserLoading(true); + try { + const userData = await getUserById(notice.clientId); + setUser(userData); + } catch (err) { + console.error("Nie udało się pobrać danych użytkownika:", err); + } finally { + setIsUserLoading(false); + } + } + }; + + fetchUser(); + }, [notice]); + + if (isLoading) { return ; } @@ -105,12 +130,17 @@ export default function NoticeDetails() { + {notice.publishDate} + + {notice.title} - - - {notice.price}zł + + + + {notice.price} zł + { if (isInWishlist) { @@ -127,6 +157,47 @@ export default function NoticeDetails() { /> + + + Kategoria: {notice.category} + + + + + Opis ogloszenia + + + {notice.description} + + + + + + Uzytkownik: + + {isUserLoading ? ( + + ) : user ? ( + + + {user.firstName} {user.lastName} + + + Email: {user.email} + + + ) : ( + Błąd podczas ładowania danych użytkownika + )} + + + + + + {notice.clientId} + + + ); From 06218dcdd40c0abc9a3a4ad4e7fb7976a3f4dace Mon Sep 17 00:00:00 2001 From: Kuba Date: Sun, 25 May 2025 20:38:35 +0200 Subject: [PATCH 02/18] Added Image Slider --- ArtisanConnect/app/notice/[id].jsx | 67 ++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index 9da0bcd..e4bed13 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -6,24 +6,27 @@ import {Image} from "@/components/ui/image"; import {Text} from "@/components/ui/text"; import {VStack} from "@/components/ui/vstack"; import {Ionicons} from "@expo/vector-icons"; -import {ActivityIndicator} from "react-native"; -import {useEffect, useState} from "react"; +import {ActivityIndicator, Dimensions, FlatList, View} from "react-native"; +import {useEffect, useState, useRef} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; import {Pressable} from "react-native"; import {getUserById} from "@/api/client"; +const {width} = Dimensions.get("window"); export default function NoticeDetails() { const {id} = useLocalSearchParams(); - const [image, setImage] = useState(null); + const [images, setImages] = useState([]); const [isImageLoading, setIsImageLoading] = useState(true); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [user, setUser] = useState(null); const [isUserLoading, setIsUserLoading] = useState(true); + const flatListRef = useRef(null); + const [currentIndex, setCurrentIndex] = useState(0); const {getNoticeById, getAllImagesByNoticeId} = useNoticesStore(); @@ -32,6 +35,15 @@ export default function NoticeDetails() { const isInWishlist = useWishlist((state) => notice ? state.wishlistNotices.some((item) => item.noticeId === notice.noticeId) : false ); + const onViewableItemsChanged = useRef(({ viewableItems }) => { + if (viewableItems.length > 0) { + setCurrentIndex(viewableItems[0].index); + } + }).current; + + const viewabilityConfig = useRef({ + itemVisiblePercentThreshold: 70, + }).current; useEffect(() => { const fetchNotice = async () => { @@ -59,8 +71,9 @@ export default function NoticeDetails() { setIsImageLoading(true); if (notice) { try { - const images = await getAllImagesByNoticeId(notice.noticeId); - setImage(images && images.length > 0 ? images[0] : "https://http.cat/404.jpg"); + const fetchedImages = await getAllImagesByNoticeId(notice.noticeId); + setImages(fetchedImages && fetchedImages.length > 0 ? fetchedImages : ["https://http.cat/404.jpg"]); + } catch (err) { console.error("Error while loading images:", err); setImage("https://http.cat/404.jpg"); @@ -118,16 +131,44 @@ export default function NoticeDetails() { ) : ( - image + + ( + + {`Zdjęcie + + )} + keyExtractor={(item, index) => index.toString()} + /> + + {images.length > 1 && ( + + {images.map((_, index) => ( + + ))} + + )} + )} + {notice.publishDate} From 925dde0bb0c26ea4a13d7b2f737d69687f718feb Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 27 May 2025 20:55:31 +0200 Subject: [PATCH 03/18] Added scrollview and send message button to notice --- ArtisanConnect/app/notice/[id].jsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index e4bed13..20f1e37 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -10,7 +10,7 @@ import {ActivityIndicator, Dimensions, FlatList, View} from "react-native"; import {useEffect, useState, useRef} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; -import {Pressable} from "react-native"; +import {Pressable, ScrollView} from "react-native"; import {getUserById} from "@/api/client"; const {width} = Dimensions.get("window"); @@ -131,7 +131,7 @@ export default function NoticeDetails() { ) : ( - + )} - + {notice.publishDate} @@ -226,6 +228,12 @@ export default function NoticeDetails() { Email: {user.email} + setIsMessageFormVisible(true)} + className="mt-3 bg-primary-500 py-2 px-4 rounded-md" + > + Wyślij wiadomość + ) : ( Błąd podczas ładowania danych użytkownika @@ -238,8 +246,8 @@ export default function NoticeDetails() { {notice.clientId} - + ); } \ No newline at end of file From 95a632741a58e3cf8048f41489d3af74bc3a1481 Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 27 May 2025 22:28:28 +0200 Subject: [PATCH 04/18] Added send message form --- ArtisanConnect/app/notice/[id].jsx | 71 +++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index 20f1e37..7d8dac6 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -6,7 +6,7 @@ import {Image} from "@/components/ui/image"; import {Text} from "@/components/ui/text"; import {VStack} from "@/components/ui/vstack"; import {Ionicons} from "@expo/vector-icons"; -import {ActivityIndicator, Dimensions, FlatList, View} from "react-native"; +import {ActivityIndicator, Dimensions, FlatList, View, TextInput} from "react-native"; import {useEffect, useState, useRef} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; @@ -29,6 +29,19 @@ export default function NoticeDetails() { const [currentIndex, setCurrentIndex] = useState(0); + const [isMessageFormVisible, setIsMessageFormVisible] = useState(false); + const [message, setMessage] = useState(''); + const [Email, setEmail] = useState(''); + const handleSendMessage = () => { + console.log('Wiadomość do:', user?.email); + console.log('Email nadawcy:', Email); + console.log('Treść:', message); + + setIsMessageFormVisible(false); + setMessage(''); + setEmail(''); + }; + const {getNoticeById, getAllImagesByNoticeId} = useNoticesStore(); const addNoticeToWishlist = useWishlist((state) => state.addNoticeToWishlist); const removeNoticeFromWishlist = useWishlist((state) => state.removeNoticeFromWishlist); @@ -221,7 +234,16 @@ export default function NoticeDetails() { {isUserLoading ? ( ) : user ? ( - + <> + + Zdjęcie profilowe + + + {user.firstName} {user.lastName} @@ -235,6 +257,7 @@ export default function NoticeDetails() { Wyślij wiadomość + ) : ( Błąd podczas ładowania danych użytkownika )} @@ -248,6 +271,50 @@ export default function NoticeDetails() { + {isMessageFormVisible && ( + + + Wyślij wiadomość do {user?.firstName} + + Do: + + {user?.email || "Brak adresu e-mail"} + + Twój e-mail: + + + + + + setIsMessageFormVisible(false)} + className="bg-gray-300 py-2 px-4 rounded-md" + > + Anuluj + + + + Wyślij + + + + + )} + ); } \ No newline at end of file From 774b2ef192a8beb4ef5e67c251a587e75c6c66f4 Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 27 May 2025 23:51:31 +0200 Subject: [PATCH 05/18] improved display of the time of adding notice --- ArtisanConnect/app/notice/[id].jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index 7d8dac6..2547aac 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -42,6 +42,15 @@ export default function NoticeDetails() { setEmail(''); }; + const formatDate = (dateString) => { + const date = new Date(dateString); + return date.toLocaleDateString('pl-PL', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }); + }; + const {getNoticeById, getAllImagesByNoticeId} = useNoticesStore(); const addNoticeToWishlist = useWishlist((state) => state.addNoticeToWishlist); const removeNoticeFromWishlist = useWishlist((state) => state.removeNoticeFromWishlist); @@ -58,6 +67,7 @@ export default function NoticeDetails() { itemVisiblePercentThreshold: 70, }).current; + useEffect(() => { const fetchNotice = async () => { setIsLoading(true); @@ -186,7 +196,7 @@ export default function NoticeDetails() { > - {notice.publishDate} + {formatDate(notice.publishDate)} {notice.title} @@ -194,6 +204,7 @@ export default function NoticeDetails() { + Cena: {notice.price} zł @@ -264,11 +275,6 @@ export default function NoticeDetails() { - - - {notice.clientId} - - {isMessageFormVisible && ( From d12ae04f8ac2de20da6b8e51f29a919e7ad30645 Mon Sep 17 00:00:00 2001 From: Kuba Date: Mon, 2 Jun 2025 23:56:56 +0200 Subject: [PATCH 06/18] added the ability to see the notices of a particular user(transition from the notice) --- ArtisanConnect/app/notice/[id].jsx | 9 +++- ArtisanConnect/app/user/[userId].jsx | 69 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 ArtisanConnect/app/user/[userId].jsx diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index 2547aac..a0fad91 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -1,4 +1,4 @@ -import {Stack, useLocalSearchParams} from "expo-router"; +import {Link, Stack, useLocalSearchParams} from "expo-router"; import {Box} from "@/components/ui/box"; import {Card} from "@/components/ui/card"; import {Heading} from "@/components/ui/heading"; @@ -6,7 +6,7 @@ import {Image} from "@/components/ui/image"; import {Text} from "@/components/ui/text"; import {VStack} from "@/components/ui/vstack"; import {Ionicons} from "@expo/vector-icons"; -import {ActivityIndicator, Dimensions, FlatList, View, TextInput} from "react-native"; +import {ActivityIndicator, Dimensions, FlatList, View, TextInput, TouchableOpacity} from "react-native"; import {useEffect, useState, useRef} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; @@ -267,6 +267,11 @@ export default function NoticeDetails() { > Wyślij wiadomość + + + Zobacz więcej ogłoszeń od {user.firstName} + + ) : ( diff --git a/ArtisanConnect/app/user/[userId].jsx b/ArtisanConnect/app/user/[userId].jsx new file mode 100644 index 0000000..0f85231 --- /dev/null +++ b/ArtisanConnect/app/user/[userId].jsx @@ -0,0 +1,69 @@ +import { useLocalSearchParams } from "expo-router"; +import { useState, useEffect } from "react"; +import { FlatList, ActivityIndicator, Text } from "react-native"; +import { Box } from "@/components/ui/box"; +import { Image } from "@/components/ui/image"; +import { VStack } from "@/components/ui/vstack"; +import { Heading } from "@/components/ui/heading"; +import { getUserById } from "@/api/client"; +import { useNoticesStore } from "@/store/noticesStore"; +import { NoticeCard } from "@/components/NoticeCard"; + +export default function UserProfile() { + const { userId } = useLocalSearchParams(); + const [user, setUser] = useState(null); + const [isUserLoading, setIsUserLoading] = useState(true); + const { notices } = useNoticesStore(); + + useEffect(() => { + const fetchUser = async () => { + setIsUserLoading(true); + try { + const userData = await getUserById(Number(userId)); + setUser(userData); + } catch (err) { + console.error("Błąd podczas pobierania danych użytkownika:", err); + setUser(null); + } finally { + setIsUserLoading(false); + } + }; + fetchUser(); + }, [userId]); + + if (isUserLoading) { + return ; + } + + if (!user) { + return Nie znaleziono użytkownika; + } + + const userNotices = notices.filter(notice => notice.clientId === Number(userId)); + + return ( + + + Zdjęcie profilowe + + {user.firstName} {user.lastName} + + + {userNotices.length > 0 ? ( + } + keyExtractor={(item) => item.noticeId.toString()} + /> + ) : ( + Ten użytkownik nie ma żadnych ogłoszeń. + )} + + ); +} \ No newline at end of file From 1c57984a36946cb98c5ee43357477d33167764c8 Mon Sep 17 00:00:00 2001 From: Kuba Date: Mon, 2 Jun 2025 23:59:05 +0200 Subject: [PATCH 07/18] Improved looking --- ArtisanConnect/app/notice/[id].jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index a0fad91..f95f3c6 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -6,7 +6,7 @@ import {Image} from "@/components/ui/image"; import {Text} from "@/components/ui/text"; import {VStack} from "@/components/ui/vstack"; import {Ionicons} from "@expo/vector-icons"; -import {ActivityIndicator, Dimensions, FlatList, View, TextInput, TouchableOpacity} from "react-native"; +import {ActivityIndicator, Dimensions, FlatList, View, TextInput} from "react-native"; import {useEffect, useState, useRef} from "react"; import {useNoticesStore} from "@/store/noticesStore"; import {useWishlist} from "@/store/wishlistStore"; @@ -268,7 +268,7 @@ export default function NoticeDetails() { Wyślij wiadomość - + Zobacz więcej ogłoszeń od {user.firstName} From 8722488a45df11f0f200796b6b2fde46aa77914a Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 3 Jun 2025 00:19:12 +0200 Subject: [PATCH 08/18] Added content to account.jsx and userNotices.jsx --- .../app/(tabs)/dashboard/account.jsx | 74 +++++++++++++++++- .../app/(tabs)/dashboard/userNotices.jsx | 77 ++++++++++++++++++- 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/ArtisanConnect/app/(tabs)/dashboard/account.jsx b/ArtisanConnect/app/(tabs)/dashboard/account.jsx index 7d3093d..25d74a6 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/account.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/account.jsx @@ -1,4 +1,70 @@ -import { Text } from "@/components/ui/text"; -export default function User() { - return Użytkownik; -} +import { Link } from "expo-router"; +import {Button} from "react-native"; +import {Box} from "@/components/ui/box"; +import {Text} from "@/components/ui/text"; +import {VStack} from "@/components/ui/vstack"; +import {Image} from "@/components/ui/image"; +import {ActivityIndicator, } from "react-native"; +import {useEffect, useState} from "react"; +import {getUserById} from "@/api/client"; + +export default function Account() { + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const currentUserId = 1; // Tymczasowo, do czasu zaimplementowania logowania bo nie moge pobrac usera + + useEffect(() => { + const fetchUser = async () => { + setIsLoading(true); + try { + const userData = await getUserById(currentUserId); + setUser(userData); + } catch (err) { + console.error("Błąd podczas pobierania danych użytkownika:", err); + } finally { + setIsLoading(false); + } + }; + fetchUser(); + }, []); + + if (isLoading) { + return ; + } + + if (!user) { + return Nie udało się pobrać danych użytkownika.; + } + + return ( + + + Zdjęcie profilowe + + {user.firstName} {user.lastName} + + + + + + Moje ogłoszenia + + + Moje płatności + + + + ); +} \ No newline at end of file diff --git a/ArtisanConnect/app/(tabs)/dashboard/userNotices.jsx b/ArtisanConnect/app/(tabs)/dashboard/userNotices.jsx index 9f5ef2f..7fe0f2b 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/userNotices.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/userNotices.jsx @@ -1,4 +1,75 @@ -import { Text } from "@/components/ui/text"; +import { useNoticesStore } from "@/store/noticesStore"; +import { NoticeCard } from "@/components/NoticeCard"; +import {Button} from "react-native"; +import {Box} from "@/components/ui/box"; +import {Text} from "@/components/ui/text"; +import {VStack} from "@/components/ui/vstack"; +import {ActivityIndicator, FlatList } from "react-native"; +import {useEffect, useState} from "react"; + export default function UserNotices() { - return Użytkownik; -} + const { notices, fetchNotices } = useNoticesStore(); + const [isLoading, setIsLoading] = useState(true); + const currentUserId = 1; // Tymczasowo, do czasu zaimplementowania logowania bo nie moge pobrac usera + + useEffect(() => { + const loadNotices = async () => { + setIsLoading(true); + try { + await fetchNotices(); + } catch (err) { + console.error("Błąd podczas pobierania ogłoszeń:", err); + } finally { + setIsLoading(false); + } + }; + loadNotices(); + }, []); + + const userNotices = notices.filter(notice => notice.clientId === currentUserId); + + if (isLoading) { + return ; + } + + return ( + + Moje ogłoszenia + {userNotices.length > 0 ? ( + ( + + + + + + + + )} + keyExtractor={(item) => item.noticeId.toString()} + /> + ) : ( + Nie masz żadnych ogłoszeń. + )} + + ); +} \ No newline at end of file From f6065893d03ea95affb876f4f9bf66ad20b2e8a9 Mon Sep 17 00:00:00 2001 From: Kuba Date: Tue, 3 Jun 2025 22:21:39 +0200 Subject: [PATCH 09/18] better look for account --- .../app/(tabs)/dashboard/account.jsx | 86 +++++++++++++------ 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/ArtisanConnect/app/(tabs)/dashboard/account.jsx b/ArtisanConnect/app/(tabs)/dashboard/account.jsx index 25d74a6..91b111d 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/account.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/account.jsx @@ -1,5 +1,5 @@ import { Link } from "expo-router"; -import {Button} from "react-native"; +import {Pressable} from "react-native"; import {Box} from "@/components/ui/box"; import {Text} from "@/components/ui/text"; import {VStack} from "@/components/ui/vstack"; @@ -7,11 +7,12 @@ import {Image} from "@/components/ui/image"; import {ActivityIndicator, } from "react-native"; import {useEffect, useState} from "react"; import {getUserById} from "@/api/client"; +import {HStack} from "@gluestack-ui/themed"; export default function Account() { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); - const currentUserId = 1; // Tymczasowo, do czasu zaimplementowania logowania bo nie moge pobrac usera + const currentUserId = 2; // Tymczasowo, do czasu zaimplementowania logowania bo nie moge pobrac usera useEffect(() => { const fetchUser = async () => { @@ -37,34 +38,67 @@ export default function Account() { } return ( - - - Zdjęcie profilowe - + + + + Zdjęcie profilowe + + + {user.firstName} {user.lastName} - - - - - Moje ogłoszenia - - - Moje płatności - + console.log("Edytuj dane użytkownika")} + > + Edytuj profil + + + + Moje dane + + + E-mail + {user.email || "brak danych"} + + + + + + Moje konto + + + + Moje ogłoszenia + + + + + {/*Tak dodałem, można zmienić na coś innego*/} + + + Historia płatności + + + + + + Ustawienia powiadomień + + + + + + Wyloguj się + ); } \ No newline at end of file From 8dc51fafdf9982055012070302c43ae36be2ed09 Mon Sep 17 00:00:00 2001 From: Andrii Solianyk Date: Wed, 4 Jun 2025 22:09:03 +0200 Subject: [PATCH 10/18] AUTORYZACJA DO GOOOOOGLA DZIALA --- ArtisanConnect/app.json | 18 ++++++++-- ArtisanConnect/app/(tabs)/login.jsx | 55 +++++++++++++++++++++++++++-- ArtisanConnect/eas.json | 21 +++++++++++ ArtisanConnect/package.json | 5 +-- 4 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 ArtisanConnect/eas.json diff --git a/ArtisanConnect/app.json b/ArtisanConnect/app.json index 71e8089..ba2a2c7 100644 --- a/ArtisanConnect/app.json +++ b/ArtisanConnect/app.json @@ -14,13 +14,19 @@ "backgroundColor": "#ffffff" }, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.hamx.ArtisanConnect" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "permissions": [ + "android.permission.RECORD_AUDIO", + "android.permission.CAMERA" + ], + "package": "com.hamx.ArtisanConnect" }, "web": { "favicon": "./assets/favicon.png" @@ -40,6 +46,12 @@ } ], "expo-web-browser" - ] + ], + "extra": { + "router": {}, + "eas": { + "projectId": "7a0d8bc8-938f-4d2a-babb-945faee13429" + } + } } } diff --git a/ArtisanConnect/app/(tabs)/login.jsx b/ArtisanConnect/app/(tabs)/login.jsx index 0fbbf82..c83452d 100644 --- a/ArtisanConnect/app/(tabs)/login.jsx +++ b/ArtisanConnect/app/(tabs)/login.jsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {StyleSheet, ActivityIndicator, SafeAreaView, View} from 'react-native'; import {useAuthStore} from '@/store/authStore'; import {useRouter, Link} from 'expo-router'; @@ -15,12 +15,27 @@ import {ArrowRightIcon} from "@/components/ui/icon" import {Divider} from '@/components/ui/divider'; import {Ionicons} from "@expo/vector-icons"; +import * as WebBrowser from 'expo-web-browser'; +import * as Google from "expo-auth-session/providers/google"; +import AsyncStorage from "@react-native-async-storage/async-storage"; + +WebBrowser.maybeCompleteAuthSession(); + +// client_id ios 936418008320-ohefdfcebd41f6oa2o8phh1mgj9s49sl.apps.googleusercontent.com +// android 936418008320-d8dfjph5e4r28fcm1rbdfbh5phmbg03d.apps.googleusercontent.com + export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const {signIn, isLoading} = useAuthStore(); const router = useRouter(); + const [request, response, promptAsync] = Google.useAuthRequest({ + androidClientId: "936418008320-d8dfjph5e4r28fcm1rbdfbh5phmbg03d.apps.googleusercontent.com", + iosClientId: "936418008320-ohefdfcebd41f6oa2o8phh1mgj9s49sl.apps.googleusercontent.com", + webClientId: "936418008320-btdngtlfnjac1p67guje72m9el5q59a7.apps.googleusercontent.com" + }) + const handleInternalLogin = async () => { if (!email || !password) { alert('Proszę wprowadzić email i hasło.'); @@ -36,6 +51,42 @@ export default function Login() { } } + useEffect(() => { + handleGoogleLogin(); + }, [response]); + + const handleGoogleLogin = async () => { + const user = await AsyncStorage.getItem("@user"); + if (!user) { + if(response?.type === "success") { + await getUserInfo(response.authentication.accessToken) + } + } else { + // Set the user in the auth store + } + }; + + const getUserInfo = async (token) => { + if(!token) { + return + } + try { + const response = await fetch("https://www.googleapis.com/userinfo/v2/me", + { + headers: { + Authorization: `Bearer ${token}` + }, + } + ); + const user = await response.json(); + console.log("Pobrano informacje o użytkowniku:", user); + await AsyncStorage.setItem("@user", JSON.stringify(user)); + } catch (error) { + console.error("Błąd podczas pobierania informacji o użytkowniku:", error); + throw error; + } + } + if (isLoading) { return ( @@ -82,7 +133,7 @@ export default function Login() { - diff --git a/ArtisanConnect/eas.json b/ArtisanConnect/eas.json new file mode 100644 index 0000000..a614f19 --- /dev/null +++ b/ArtisanConnect/eas.json @@ -0,0 +1,21 @@ +{ + "cli": { + "version": ">= 16.8.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json index f44ffd7..72d3615 100644 --- a/ArtisanConnect/package.json +++ b/ArtisanConnect/package.json @@ -53,11 +53,12 @@ "react-native-gesture-handler": "~2.24.0", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.10.0", + "react-native-screens": "~4.11.1", "react-native-svg": "15.11.2", "react-native-web": "~0.20.0", "tailwindcss": "^3.4.17", - "zustand": "^5.0.3" + "zustand": "^5.0.3", + "expo-crypto": "~14.1.4" }, "devDependencies": { "@babel/core": "^7.20.0", From b6f2225148e47d70f45b12258379730a1779b510 Mon Sep 17 00:00:00 2001 From: Andrii Solianyk Date: Wed, 4 Jun 2025 22:25:31 +0200 Subject: [PATCH 11/18] scheme fix --- ArtisanConnect/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ArtisanConnect/app.json b/ArtisanConnect/app.json index ba2a2c7..fab36c1 100644 --- a/ArtisanConnect/app.json +++ b/ArtisanConnect/app.json @@ -2,7 +2,7 @@ "expo": { "name": "ArtisanConnect", "slug": "ArtisanConnect", - "scheme": "Artisanconnect", + "scheme": "ArtisanConnect", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", From 48cf5cd6c4c9c7652f0409d0c4dcbc8c6fc0ec10 Mon Sep 17 00:00:00 2001 From: JaPatryk Date: Thu, 5 Jun 2025 21:40:30 +0200 Subject: [PATCH 12/18] fix integration --- ArtisanConnect/api/categories.jsx | 7 +++- ArtisanConnect/api/notices.jsx | 4 +- ArtisanConnect/api/wishlist.jsx | 15 +++++--- .../app/(tabs)/dashboard/account.jsx | 14 ++++++- ArtisanConnect/app/(tabs)/index.jsx | 18 ++++++++- ArtisanConnect/app/_layout.jsx | 12 +++--- ArtisanConnect/components/CategorySection.jsx | 38 +++++++++++++------ ArtisanConnect/components/UserSection.jsx | 11 ++++-- ArtisanConnect/store/authStore.jsx | 6 ++- 9 files changed, 92 insertions(+), 33 deletions(-) diff --git a/ArtisanConnect/api/categories.jsx b/ArtisanConnect/api/categories.jsx index ae3d001..82f4a49 100644 --- a/ArtisanConnect/api/categories.jsx +++ b/ArtisanConnect/api/categories.jsx @@ -1,10 +1,15 @@ import axios from "axios"; +import {useAuthStore} from "@/store/authStore"; const API_URL = "https://testowe.zikor.pl/api/v1"; export async function listCategories() { + + const { token } = useAuthStore.getState(); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + try { - const response = await axios.get(`${API_URL}/vars/categories`); + const response = await axios.get(`${API_URL}/vars/categories`, { headers }); return response.data; } catch (err) { console.error("Nie udało się pobrać listy kategorii.", err.response.status); diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx index 55cb0d8..280ffbc 100644 --- a/ArtisanConnect/api/notices.jsx +++ b/ArtisanConnect/api/notices.jsx @@ -2,9 +2,9 @@ import axios from "axios"; import FormData from 'form-data' import {useAuthStore} from "@/store/authStore"; -// const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://testowe.zikor.pl/api/v1"; -const API_URL = "http://10.0.2.2:8080/api/v1"; +//const API_URL = "http://10.0.2.2:8080/api/v1"; export async function listNotices() { const { token } = useAuthStore.getState(); diff --git a/ArtisanConnect/api/wishlist.jsx b/ArtisanConnect/api/wishlist.jsx index 9e5996f..78d0d4d 100644 --- a/ArtisanConnect/api/wishlist.jsx +++ b/ArtisanConnect/api/wishlist.jsx @@ -1,14 +1,16 @@ import axios from "axios"; +import {useAuthStore} from "@/store/authStore"; // import FormData from 'form-data' const API_URL = "https://testowe.zikor.pl/api/v1/wishlist"; export async function toggleNoticeStatus(noticeId) { + const { token } = useAuthStore.getState(); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + try { - const response = await axios.post(`${API_URL}/toggle/${noticeId}`, null, { - headers: { - "Content-Type": "application/json", - }, + const response = await axios.post(`${API_URL}/toggle/${noticeId}`, {}, { + headers }); return response.data; } catch (error) { @@ -18,8 +20,11 @@ export async function toggleNoticeStatus(noticeId) { } export async function getWishlist() { + const { token } = useAuthStore.getState(); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + try { - const response = await axios.get(`${API_URL}/`); + const response = await axios.get(`${API_URL}/`, {headers}); console.log("Wishlist response:", response.data); return response.data; } catch (error) { diff --git a/ArtisanConnect/app/(tabs)/dashboard/account.jsx b/ArtisanConnect/app/(tabs)/dashboard/account.jsx index 7d3093d..a6bff6d 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/account.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/account.jsx @@ -1,4 +1,16 @@ import { Text } from "@/components/ui/text"; +import { View } from "react-native"; +import { Button, ButtonText } from "@gluestack-ui/themed"; +import { useAuthStore } from "@/store/authStore"; + export default function User() { - return Użytkownik; + const signOut = useAuthStore((state) => state.signOut); + + return ( + Użytkownik + + + ) } diff --git a/ArtisanConnect/app/(tabs)/index.jsx b/ArtisanConnect/app/(tabs)/index.jsx index f8d6111..31bf531 100644 --- a/ArtisanConnect/app/(tabs)/index.jsx +++ b/ArtisanConnect/app/(tabs)/index.jsx @@ -7,11 +7,14 @@ import { SearchSection } from "@/components/SearchSection"; import { FlatList } from 'react-native'; import { useAuthStore } from "@/store/authStore"; import { useRouter } from "expo-router"; -import { useEffect, useState } from "react"; +import { useEffect, useState } from "react";; + + export default function Home() { const token = useAuthStore((state) => state.token); const router = useRouter(); const [isReady, setIsReady] = useState(false); + const fetchNotices = useNoticesStore((state) => state.fetchNotices); useEffect(() => { setIsReady(true); @@ -23,14 +26,25 @@ const token = useAuthStore((state) => state.token); } }, [isReady, token, router]); + + useEffect(() => { + if (token) { + fetchNotices(); + } +}, [token, fetchNotices]); + + const notices = useNoticesStore((state) => state.notices); - const latestNotices = [...notices] + // console.log("Notices:", notices); + + const latestNotices = [...notices] .sort((a, b) => new Date(b.publishDate) - new Date(a.publishDate)) .slice(0, 6); const recomendedNotices = [...notices] .sort(() => Math.random() - 0.5) .slice(0, 6); + return ( diff --git a/ArtisanConnect/app/_layout.jsx b/ArtisanConnect/app/_layout.jsx index 4569df8..237a9c7 100644 --- a/ArtisanConnect/app/_layout.jsx +++ b/ArtisanConnect/app/_layout.jsx @@ -2,16 +2,16 @@ import { Stack, Redirect } from "expo-router"; import "@/global.css"; import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { useEffect, useState } from "react"; -import { useNoticesStore } from "@/store/noticesStore"; +// import { useEffect, useState } from "react"; +// import { useNoticesStore } from "@/store/noticesStore"; const queryClient = new QueryClient(); export default function RootLayout() { - const fetchNotices = useNoticesStore((state) => state.fetchNotices); + // const fetchNotices = useNoticesStore((state) => state.fetchNotices); - useEffect(() => { - fetchNotices(); - }, []); + // useEffect(() => { + // fetchNotices(); + // }, []); return ( diff --git a/ArtisanConnect/components/CategorySection.jsx b/ArtisanConnect/components/CategorySection.jsx index ed6de5f..7698adc 100644 --- a/ArtisanConnect/components/CategorySection.jsx +++ b/ArtisanConnect/components/CategorySection.jsx @@ -1,30 +1,46 @@ import { View, FlatList} from 'react-native'; import { useEffect, useState } from 'react' +import { useAuthStore } from "@/store/authStore"; import { Heading } from '@/components/ui/heading'; import { Text } from '@/components/ui/text'; import { Link } from 'expo-router'; import { Pressable } from '@/components/ui/pressable'; -import axios from 'axios'; +// import axios from 'axios'; +import {listCategories} from "@/api/categories"; + export function CategorySection({notices, title}) { -// const notices = useNoticesStore((state) => state.notices); - + const token = useAuthStore((state) => state.token); + const [categoryMap, setCategoryMap] = useState({}); useEffect(() => { - axios.get('https://testowe.zikor.pl/api/v1/vars/categories') - .then(res => setCategoryMap(res.data)) - .catch(() => setCategoryMap({})); - }, []); - -const categories = Array.from( - new Set(notices.map((notice) => notice.category)) -).filter(Boolean); + if(token){ + const fetchCategories = async () => { + let data = await listCategories(); + if (Array.isArray(data)) { + setCategoryMap(data); + } + } + fetchCategories(); + } + }, [token]); + + + const categories = Array.from( + new Set(notices.map((notice) => notice.category)) + ).filter(Boolean); const getCount = (category) => notices.filter((notice) => notice.category === category).length; + console.log("CategoryMap:", categoryMap); + + if(!categoryMap) { + return null; + } + return ( {title} diff --git a/ArtisanConnect/components/UserSection.jsx b/ArtisanConnect/components/UserSection.jsx index db42d59..69ea77e 100644 --- a/ArtisanConnect/components/UserSection.jsx +++ b/ArtisanConnect/components/UserSection.jsx @@ -4,16 +4,21 @@ import { Heading } from '@/components/ui/heading'; import { FlatList } from 'react-native'; import axios from 'axios'; import UserBlock from '@/components/UserBlock'; +import {useAuthStore} from "@/store/authStore"; + export function UserSection({notices, title}) { - + const token = useAuthStore((state) => state.token); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; const [users, setUsers] = useState([]); useEffect(() => { - axios.get('https://testowe.zikor.pl/api/v1/clients/get/all') + if (token){ + axios.get('https://testowe.zikor.pl/api/v1/clients/get/all', { headers }) .then(res => setUsers(res.data)) .catch(() => setUsers([])); - }, []); + } + }, [token]); const usersWithNoticeCount = users.map(user => { const count = notices.filter(n => n.clientId === user.id).length; diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index f205790..0bb66c6 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -3,7 +3,7 @@ import {createJSONStorage, persist} from "zustand/middleware"; import AsyncStorage from "@react-native-async-storage/async-storage"; import axios from "axios"; -const API_URL = "http://10.0.2.2:8080/api/v1"; +const API_URL = "https://testowe.zikor.pl/api/v1"; export const useAuthStore = create( persist( @@ -71,8 +71,10 @@ export const useAuthStore = create( signOut: async () => { try { + const {token} = useAuthStore.getState(); + const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; // Можно отправить запрос на бэкенд для инвалидации токена - await axios.post(`${API_URL}/auth/logout`); + await axios.post(`${API_URL}/auth/logout`, {}, { headers }); } catch (error) { console.error("Logout error:", error); } finally { From d4133f28bdf80cc54ca68c5ad5599434fca192af Mon Sep 17 00:00:00 2001 From: Andrii Solianyk Date: Fri, 6 Jun 2025 15:49:08 +0200 Subject: [PATCH 13/18] =?UTF-8?q?autoryzacja=20dzia=C5=82a=20zar=C3=B3wno?= =?UTF-8?q?=20na=20android=20jak=20i=20ios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArtisanConnect/api/categories.jsx | 2 +- ArtisanConnect/api/notices.jsx | 21 ++++++---------- ArtisanConnect/app.json | 6 ++--- ArtisanConnect/app/(tabs)/login.jsx | 31 +++++++++++++++++------ ArtisanConnect/package.json | 2 +- ArtisanConnect/store/authStore.jsx | 39 +++++++++++++---------------- 6 files changed, 53 insertions(+), 48 deletions(-) diff --git a/ArtisanConnect/api/categories.jsx b/ArtisanConnect/api/categories.jsx index ae3d001..0b9bd47 100644 --- a/ArtisanConnect/api/categories.jsx +++ b/ArtisanConnect/api/categories.jsx @@ -1,6 +1,6 @@ import axios from "axios"; -const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export async function listCategories() { try { diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx index 42776df..ee84069 100644 --- a/ArtisanConnect/api/notices.jsx +++ b/ArtisanConnect/api/notices.jsx @@ -4,18 +4,15 @@ import {useAuthStore} from "@/store/authStore"; // const API_URL = "https://testowe.zikor.pl/api/v1"; -const API_URL = "http://10.0.2.2:8080/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export async function listNotices() { const { token } = useAuthStore.getState(); const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; - console.log(token); - const response = await fetch(`${API_URL}/notices/get/all`, { headers: headers }); - console.log(response); const data = await response.json(); if (!response.ok) { throw new Error(response.toString()); @@ -62,8 +59,6 @@ export async function getImageByNoticeId(noticeId) { const imageName = listResponse.data[0]; imageUrl = `${API_URL}/images/get/${imageName}`; - console.log(`Pobrano zdjęcie o nazwie: ${imageName}`); - return imageUrl; } catch (err) { console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); @@ -77,25 +72,23 @@ export async function getAllImagesByNoticeId(noticeId) { const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); if (listResponse.data && listResponse.data.length > 0) { - const imageUrls = listResponse.data.map(imageName => + return listResponse.data.map(imageName => `${API_URL}/images/get/${imageName}` ); - - console.log(`Pobrano ${imageUrls.length} zdjęć dla ogłoszenia o id: ${noticeId}`); - return imageUrls; } - console.log(`Brak zdjęć dla ogłoszenia o id: ${noticeId}`); return ["https://http.cat/404.jpg"]; } catch (err) { - console.log(`Błąd podczas pobierania listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); + if(err.response.status === 404) { + console.info(`Ogłoszenie o id: ${noticeId} nie posiada zdjęć.`); + return ["https://http.cat/404.jpg"]; + } + console.warn(`Nie udało się pobrać listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); return ["https://http.cat/404.jpg"]; } } export const uploadImage = async (noticeId, imageUri) => { - console.log(imageUri); - const formData = new FormData(); const filename = imageUri.split('/').pop(); diff --git a/ArtisanConnect/app.json b/ArtisanConnect/app.json index fab36c1..bbe332e 100644 --- a/ArtisanConnect/app.json +++ b/ArtisanConnect/app.json @@ -2,7 +2,7 @@ "expo": { "name": "ArtisanConnect", "slug": "ArtisanConnect", - "scheme": "ArtisanConnect", + "scheme": "com.hamx.artisanconnect", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", @@ -15,7 +15,7 @@ }, "ios": { "supportsTablet": true, - "bundleIdentifier": "com.hamx.ArtisanConnect" + "bundleIdentifier": "com.hamx.artisanconnect" }, "android": { "adaptiveIcon": { @@ -26,7 +26,7 @@ "android.permission.RECORD_AUDIO", "android.permission.CAMERA" ], - "package": "com.hamx.ArtisanConnect" + "package": "com.hamx.artisanconnect" }, "web": { "favicon": "./assets/favicon.png" diff --git a/ArtisanConnect/app/(tabs)/login.jsx b/ArtisanConnect/app/(tabs)/login.jsx index c83452d..fa802de 100644 --- a/ArtisanConnect/app/(tabs)/login.jsx +++ b/ArtisanConnect/app/(tabs)/login.jsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from 'react'; -import {StyleSheet, ActivityIndicator, SafeAreaView, View} from 'react-native'; +import {StyleSheet, ActivityIndicator, SafeAreaView, View, Platform} from 'react-native'; import {useAuthStore} from '@/store/authStore'; import {useRouter, Link} from 'expo-router'; @@ -18,6 +18,9 @@ import {Ionicons} from "@expo/vector-icons"; import * as WebBrowser from 'expo-web-browser'; import * as Google from "expo-auth-session/providers/google"; import AsyncStorage from "@react-native-async-storage/async-storage"; +import {makeRedirectUri} from "expo-auth-session"; + +import Constants from 'expo-constants'; WebBrowser.maybeCompleteAuthSession(); @@ -27,13 +30,20 @@ WebBrowser.maybeCompleteAuthSession(); export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const {signIn, isLoading} = useAuthStore(); + const {signIn, isLoading, signInWithGoogle} = useAuthStore(); const router = useRouter(); const [request, response, promptAsync] = Google.useAuthRequest({ androidClientId: "936418008320-d8dfjph5e4r28fcm1rbdfbh5phmbg03d.apps.googleusercontent.com", iosClientId: "936418008320-ohefdfcebd41f6oa2o8phh1mgj9s49sl.apps.googleusercontent.com", - webClientId: "936418008320-btdngtlfnjac1p67guje72m9el5q59a7.apps.googleusercontent.com" + webClientId: "936418008320-btdngtlfnjac1p67guje72m9el5q59a7.apps.googleusercontent.com", + redirectUri: + Platform.OS === 'android' + ? makeRedirectUri({ + scheme: Constants.expoConfig.android.package, + path: '/', + }) + : undefined, }) const handleInternalLogin = async () => { @@ -56,13 +66,18 @@ export default function Login() { }, [response]); const handleGoogleLogin = async () => { - const user = await AsyncStorage.getItem("@user"); + // const user = await AsyncStorage.getItem("@user"); + let user = null; if (!user) { - if(response?.type === "success") { - await getUserInfo(response.authentication.accessToken) + if(response.type === "success") { + user = await getUserInfo(response.authentication.accessToken) + await signInWithGoogle(response.authentication.accessToken); + alert(`Zalogowano jako ${user.email}`); } + } else { - // Set the user in the auth store + console.info("Pobrano użytkownika z AsyncStorage:", JSON.parse(user)); + alert(`Zalogowano jako ${user.email}`); } }; @@ -79,8 +94,8 @@ export default function Login() { } ); const user = await response.json(); - console.log("Pobrano informacje o użytkowniku:", user); await AsyncStorage.setItem("@user", JSON.stringify(user)); + return user; } catch (error) { console.error("Błąd podczas pobierania informacji o użytkowniku:", error); throw error; diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json index 72d3615..f54d0dc 100644 --- a/ArtisanConnect/package.json +++ b/ArtisanConnect/package.json @@ -36,7 +36,7 @@ "expo": "^53.0.0", "expo-auth-session": "~6.1.5", "expo-camera": "~16.1.6", - "expo-constants": "~17.1.5", + "expo-constants": "~17.1.6", "expo-image-picker": "~16.1.4", "expo-linking": "~7.1.4", "expo-router": "~5.0.5", diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index f205790..67425ab 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -3,12 +3,12 @@ import {createJSONStorage, persist} from "zustand/middleware"; import AsyncStorage from "@react-native-async-storage/async-storage"; import axios from "axios"; -const API_URL = "http://10.0.2.2:8080/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export const useAuthStore = create( persist( (set) => ({ - user: null, + user_id: null, token: null, isLoading: false, error: null, @@ -21,9 +21,11 @@ export const useAuthStore = create( password }); - const user = response.data.user; + const user_id = response.data.user_id; const token = response.data.token; - set({user, token, isLoading: false}); + set({user_id: user_id, token: token, isLoading: false}); + + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; } catch (error) { set({error: error.response?.data?.message || error.message, isLoading: false}); throw error; @@ -33,19 +35,15 @@ export const useAuthStore = create( signUp: async (userData) => { set({isLoading: true, error: null}); try { - console.log(userData); - const response = await axios.post(`${API_URL}/auth/register`, userData, { headers: {'Content-Type': 'application/json'} }); - console.log(response.data); - - const user = response.data.user; + const user_id = response.data.user_id; const token = response.data.token; - set({user, token, isLoading: false}); + set({user_id: user_id, token: token, isLoading: false}); - return user; + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; } catch (error) { set({error: error.response?.data?.message || error.message, isLoading: false}); throw error; @@ -55,14 +53,14 @@ export const useAuthStore = create( signInWithGoogle: async (googleToken) => { set({isLoading: true, error: null}); try { - const response = await axios.post(`${API_URL}/auth/google`, {token: googleToken}); - - const {user, token} = response.data; - set({user, token, isLoading: false}); + const response = await axios.post(`${API_URL}/auth/google`, {googleToken: googleToken}, { + headers: {'Content-Type': 'application/json'} + }); + const user_id = response.data.user_id; + const token = response.data.token; + set({user_id: user_id, token: token, isLoading: false}); axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - - return user; } catch (error) { set({error: error.response?.data?.message || error.message, isLoading: false}); throw error; @@ -71,13 +69,12 @@ export const useAuthStore = create( signOut: async () => { try { - // Можно отправить запрос на бэкенд для инвалидации токена await axios.post(`${API_URL}/auth/logout`); } catch (error) { console.error("Logout error:", error); } finally { delete axios.defaults.headers.common["Authorization"]; - set({user: null, token: null}); + set({user_id: null, token: null}); } }, @@ -91,11 +88,11 @@ export const useAuthStore = create( const response = await axios.get(`${API_URL}/auth/me`); - set({user: response.data, isLoading: false}); + set({user_id: response.data, isLoading: false}); return response.data; } catch (error) { delete axios.defaults.headers.common["Authorization"]; - set({user: null, token: null, isLoading: false}); + set({user_id: null, token: null, isLoading: false}); return null; } }, From df44742a7b1f6f0c4662a38e585fa29b8e98770f Mon Sep 17 00:00:00 2001 From: JaPatryk Date: Fri, 6 Jun 2025 20:36:15 +0200 Subject: [PATCH 14/18] safe area view --- ArtisanConnect/app/(tabs)/index.jsx | 13 ++++++++----- ArtisanConnect/app/_layout.jsx | 8 -------- ArtisanConnect/components/SearchSection.jsx | 8 ++++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/ArtisanConnect/app/(tabs)/index.jsx b/ArtisanConnect/app/(tabs)/index.jsx index 31bf531..5f4647b 100644 --- a/ArtisanConnect/app/(tabs)/index.jsx +++ b/ArtisanConnect/app/(tabs)/index.jsx @@ -7,8 +7,9 @@ import { SearchSection } from "@/components/SearchSection"; import { FlatList } from 'react-native'; import { useAuthStore } from "@/store/authStore"; import { useRouter } from "expo-router"; -import { useEffect, useState } from "react";; - +import { useEffect, useState } from "react"; +// import { SafeAreaView } from "react-native-safe-area-context"; +import { SafeAreaView } from "react-native"; export default function Home() { const token = useAuthStore((state) => state.token); @@ -46,14 +47,16 @@ const token = useAuthStore((state) => state.token); return ( - + + {/* */} - + - + {/* */} + ); } diff --git a/ArtisanConnect/app/_layout.jsx b/ArtisanConnect/app/_layout.jsx index 237a9c7..599aab1 100644 --- a/ArtisanConnect/app/_layout.jsx +++ b/ArtisanConnect/app/_layout.jsx @@ -2,21 +2,13 @@ import { Stack, Redirect } from "expo-router"; import "@/global.css"; import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -// import { useEffect, useState } from "react"; -// import { useNoticesStore } from "@/store/noticesStore"; const queryClient = new QueryClient(); export default function RootLayout() { - // const fetchNotices = useNoticesStore((state) => state.fetchNotices); - - // useEffect(() => { - // fetchNotices(); - // }, []); return ( - - + + - - ) } \ No newline at end of file From c19333ad8b39967caa52d5fc8e23a78c5ba387dd Mon Sep 17 00:00:00 2001 From: Patryk Date: Sat, 7 Jun 2025 23:08:21 +0200 Subject: [PATCH 15/18] fix login check and urls --- ArtisanConnect/api/categories.jsx | 25 +- ArtisanConnect/api/client.jsx | 19 +- ArtisanConnect/api/notices.jsx | 188 +++++++-------- ArtisanConnect/api/wishlist.jsx | 50 ++-- ArtisanConnect/app/(tabs)/_layout.jsx | 128 ++++++----- .../app/(tabs)/dashboard/_layout.jsx | 20 +- ArtisanConnect/app/(tabs)/index.jsx | 50 ++-- ArtisanConnect/components/CategorySection.jsx | 56 ++--- ArtisanConnect/components/UserSection.jsx | 47 ++-- ArtisanConnect/store/authStore.jsx | 215 ++++++++++-------- 10 files changed, 424 insertions(+), 374 deletions(-) diff --git a/ArtisanConnect/api/categories.jsx b/ArtisanConnect/api/categories.jsx index 82f4a49..ed76375 100644 --- a/ArtisanConnect/api/categories.jsx +++ b/ArtisanConnect/api/categories.jsx @@ -1,17 +1,16 @@ import axios from "axios"; -import {useAuthStore} from "@/store/authStore"; +import { useAuthStore } from "@/store/authStore"; -const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export async function listCategories() { - - const { token } = useAuthStore.getState(); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; - - try { - const response = await axios.get(`${API_URL}/vars/categories`, { headers }); - return response.data; - } catch (err) { - console.error("Nie udało się pobrać listy kategorii.", err.response.status); - } -} \ No newline at end of file + const { token } = useAuthStore.getState(); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + + try { + const response = await axios.get(`${API_URL}/vars/categories`, { headers }); + return response.data; + } catch (err) { + console.error("Nie udało się pobrać listy kategorii.", err.response.status); + } +} diff --git a/ArtisanConnect/api/client.jsx b/ArtisanConnect/api/client.jsx index 6f0489c..6268c0e 100644 --- a/ArtisanConnect/api/client.jsx +++ b/ArtisanConnect/api/client.jsx @@ -1,13 +1,16 @@ import axios from "axios"; -const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export async function getUserById(userId) { - try { - const response = await axios.get(`${API_URL}/clients/get/${userId}`); - return response.data; - } catch (err) { - console.error(`Nie udało się pobrać danych użytkownika o ID ${userId}.`, err.response.status); - throw err; - } + try { + const response = await axios.get(`${API_URL}/clients/get/${userId}`); + return response.data; + } catch (err) { + console.error( + `Nie udało się pobrać danych użytkownika o ID ${userId}.`, + err.response.status + ); + throw err; + } } diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx index 280ffbc..1153ea0 100644 --- a/ArtisanConnect/api/notices.jsx +++ b/ArtisanConnect/api/notices.jsx @@ -1,129 +1,133 @@ import axios from "axios"; -import FormData from 'form-data' -import {useAuthStore} from "@/store/authStore"; +import FormData from "form-data"; +import { useAuthStore } from "@/store/authStore"; -const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; //const API_URL = "http://10.0.2.2:8080/api/v1"; export async function listNotices() { - const { token } = useAuthStore.getState(); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + const { token } = useAuthStore.getState(); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; - console.log(token); + console.log(token); - const response = await fetch(`${API_URL}/notices/get/all`, { - headers: headers - }); - console.log(response); - const data = await response.json(); - if (!response.ok) { - throw new Error(response.toString()); - } - return data; + const response = await fetch(`${API_URL}/notices/get/all`, { + headers: headers, + }); + // console.log(response);r + const data = await response.json(); + if (!response.ok) { + throw new Error(response.toString()); + } + return data; } export async function getNoticeById(noticeId) { - const response = await fetch(`${API_URL}/notices/get/${noticeId}`); + const response = await fetch(`${API_URL}/notices/get/${noticeId}`); - const data = await response.json(); - if (!response.ok) { - throw new Error("Error"); - } - return data; + const data = await response.json(); + if (!response.ok) { + throw new Error("Error"); + } + return data; } export async function createNotice(notice) { - try { - const response = await axios.post(`${API_URL}/notices/add`, notice, { - headers: { - "Content-Type": "application/json", - }, - }); + try { + const response = await axios.post(`${API_URL}/notices/add`, notice, { + headers: { + "Content-Type": "application/json", + }, + }); - if (response.data.noticeId !== null) { - for (const imageUri of notice.image) { - await uploadImage(response.data.noticeId, imageUri); - } - } - - return response.data; - } catch (error) { - console.log("Error", error.response.data, error.response.status); - return null; + if (response.data.noticeId !== null) { + for (const imageUri of notice.image) { + await uploadImage(response.data.noticeId, imageUri); + } } + + return response.data; + } catch (error) { + console.log("Error", error.response.data, error.response.status); + return null; + } } export async function getImageByNoticeId(noticeId) { - let imageUrl; - try { - const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); + let imageUrl; + try { + const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); - const imageName = listResponse.data[0]; - imageUrl = `${API_URL}/images/get/${imageName}`; + const imageName = listResponse.data[0]; + imageUrl = `${API_URL}/images/get/${imageName}`; - console.log(`Pobrano zdjęcie o nazwie: ${imageName}`); + console.log(`Pobrano zdjęcie o nazwie: ${imageName}`); - return imageUrl; - } catch (err) { - console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); - imageUrl = "https://http.cat/404.jpg"; - return imageUrl; - } + return imageUrl; + } catch (err) { + console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); + imageUrl = "https://http.cat/404.jpg"; + return imageUrl; + } } export async function getAllImagesByNoticeId(noticeId) { - try { - const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); + try { + const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); - if (listResponse.data && listResponse.data.length > 0) { - const imageUrls = listResponse.data.map(imageName => - `${API_URL}/images/get/${imageName}` - ); + if (listResponse.data && listResponse.data.length > 0) { + const imageUrls = listResponse.data.map( + (imageName) => `${API_URL}/images/get/${imageName}` + ); - // console.log(`Pobrano ${imageUrls.length} zdjęć dla ogłoszenia o id: ${noticeId}`); - return imageUrls; - } - - // console.log(`Brak zdjęć dla ogłoszenia o id: ${noticeId}`); - return ["https://http.cat/404.jpg"]; - } catch (err) { - // console.log(`Błąd podczas pobierania listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); - return ["https://http.cat/404.jpg"]; + // console.log(`Pobrano ${imageUrls.length} zdjęć dla ogłoszenia o id: ${noticeId}`); + return imageUrls; } + + // console.log(`Brak zdjęć dla ogłoszenia o id: ${noticeId}`); + return ["https://http.cat/404.jpg"]; + } catch (err) { + // console.log(`Błąd podczas pobierania listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); + return ["https://http.cat/404.jpg"]; + } } export const uploadImage = async (noticeId, imageUri) => { - console.log(imageUri); + console.log(imageUri); - const formData = new FormData(); + const formData = new FormData(); - const filename = imageUri.split('/').pop(); + const filename = imageUri.split("/").pop(); - const match = /\.(\w+)$/.exec(filename); - const type = match ? `image/${match[1]}` : 'image/jpeg'; + const match = /\.(\w+)$/.exec(filename); + const type = match ? `image/${match[1]}` : "image/jpeg"; - formData.append('file', { - uri: imageUri, - name: filename, - type: type, - }); + formData.append("file", { + uri: imageUri, + name: filename, + type: type, + }); - try { - const response = await axios.post( - `${API_URL}/images/upload/${noticeId}`, - formData, - { - headers: { - 'Content-Type': 'multipart/form-data', - }, - } - ); - console.info('Upload successful:', response.data); - return response.data; - } catch (error) { - console.log("imageURI:", imageUri); - console.error('Error uploading image:', error.response.data, error.response.status); - throw error; - } -} \ No newline at end of file + try { + const response = await axios.post( + `${API_URL}/images/upload/${noticeId}`, + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + console.info("Upload successful:", response.data); + return response.data; + } catch (error) { + console.log("imageURI:", imageUri); + console.error( + "Error uploading image:", + error.response.data, + error.response.status + ); + throw error; + } +}; diff --git a/ArtisanConnect/api/wishlist.jsx b/ArtisanConnect/api/wishlist.jsx index 78d0d4d..265fc71 100644 --- a/ArtisanConnect/api/wishlist.jsx +++ b/ArtisanConnect/api/wishlist.jsx @@ -1,34 +1,38 @@ import axios from "axios"; -import {useAuthStore} from "@/store/authStore"; +import { useAuthStore } from "@/store/authStore"; // import FormData from 'form-data' -const API_URL = "https://testowe.zikor.pl/api/v1/wishlist"; +const API_URL = "https://hopp.zikor.pl/api/v1/wishlist"; export async function toggleNoticeStatus(noticeId) { - const { token } = useAuthStore.getState(); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + const { token } = useAuthStore.getState(); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; - try { - const response = await axios.post(`${API_URL}/toggle/${noticeId}`, {}, { - headers - }); - return response.data; - } catch (error) { - console.error("Error toggling wishlist item:", error); - throw error; - } + try { + const response = await axios.post( + `${API_URL}/toggle/${noticeId}`, + {}, + { + headers, + } + ); + return response.data; + } catch (error) { + console.error("Error toggling wishlist item:", error); + throw error; + } } export async function getWishlist() { - const { token } = useAuthStore.getState(); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; + const { token } = useAuthStore.getState(); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; - try { - const response = await axios.get(`${API_URL}/`, {headers}); - console.log("Wishlist response:", response.data); - return response.data; - } catch (error) { - console.error("Error fetching wishlist:", error); - throw error; - } + try { + const response = await axios.get(`${API_URL}/`, { headers }); + console.log("Wishlist response:", response.data); + return response.data; + } catch (error) { + console.error("Error fetching wishlist:", error); + throw error; + } } diff --git a/ArtisanConnect/app/(tabs)/_layout.jsx b/ArtisanConnect/app/(tabs)/_layout.jsx index 7f9d212..4a42cf7 100644 --- a/ArtisanConnect/app/(tabs)/_layout.jsx +++ b/ArtisanConnect/app/(tabs)/_layout.jsx @@ -1,64 +1,70 @@ -import {Tabs} from "expo-router"; -import {Ionicons} from "@expo/vector-icons"; +import { Tabs, Redirect } from "expo-router"; +import { Ionicons } from "@expo/vector-icons"; +import { useAuthStore } from "@/store/authStore"; export default function TabLayout() { - return ( - - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - - ); + const token = useAuthStore((state) => state.token); + + if (!token) { + return ; + } + return ( + + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); } diff --git a/ArtisanConnect/app/(tabs)/dashboard/_layout.jsx b/ArtisanConnect/app/(tabs)/dashboard/_layout.jsx index df44641..980668e 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/_layout.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/_layout.jsx @@ -1,6 +1,15 @@ +import { DrawerItem } from "@react-navigation/drawer"; import { Drawer } from "expo-router/drawer"; +import { useAuthStore } from "@/store/authStore"; + +import { + DrawerContentScrollView, + DrawerItemList, +} from "@react-navigation/drawer"; export default function AccountDrawerLayout() { + const signOut = useAuthStore((state) => state.signOut); + return ( ( + + + + + )} > state.token); + const token = useAuthStore((state) => state.token); const router = useRouter(); const [isReady, setIsReady] = useState(false); - const fetchNotices = useNoticesStore((state) => state.fetchNotices); + const fetchNotices = useNoticesStore((state) => state.fetchNotices); useEffect(() => { setIsReady(true); @@ -27,36 +26,41 @@ const token = useAuthStore((state) => state.token); } }, [isReady, token, router]); - useEffect(() => { if (token) { - fetchNotices(); - } -}, [token, fetchNotices]); - + fetchNotices(); + } + }, [token, fetchNotices]); const notices = useNoticesStore((state) => state.notices); - // console.log("Notices:", notices); - + // console.log("Notices:", notices); + const latestNotices = [...notices] .sort((a, b) => new Date(b.publishDate) - new Date(a.publishDate)) .slice(0, 6); - const recomendedNotices = [...notices] + const recomendedNotices = [...notices] .sort(() => Math.random() - 0.5) .slice(0, 6); - return ( - - {/* */} - - + + {/* */} + + - + - - - {/* */} - + + + {/* */} + ); } diff --git a/ArtisanConnect/components/CategorySection.jsx b/ArtisanConnect/components/CategorySection.jsx index 7698adc..2d9321e 100644 --- a/ArtisanConnect/components/CategorySection.jsx +++ b/ArtisanConnect/components/CategorySection.jsx @@ -1,32 +1,25 @@ -import { View, FlatList} from 'react-native'; -import { useEffect, useState } from 'react' +import { View, FlatList } from "react-native"; +import { useEffect, useState } from "react"; import { useAuthStore } from "@/store/authStore"; -import { Heading } from '@/components/ui/heading'; -import { Text } from '@/components/ui/text'; -import { Link } from 'expo-router'; -import { Pressable } from '@/components/ui/pressable'; -// import axios from 'axios'; -import {listCategories} from "@/api/categories"; +import { Heading } from "@/components/ui/heading"; +import { Text } from "@/components/ui/text"; +import { Link } from "expo-router"; +import { Pressable } from "@/components/ui/pressable"; +import { listCategories } from "@/api/categories"; - -export function CategorySection({notices, title}) { - const token = useAuthStore((state) => state.token); - +export function CategorySection({ notices, title }) { const [categoryMap, setCategoryMap] = useState({}); useEffect(() => { - if(token){ const fetchCategories = async () => { - let data = await listCategories(); - if (Array.isArray(data)) { - setCategoryMap(data); - } - } + let data = await listCategories(); + if (Array.isArray(data)) { + setCategoryMap(data); + } + }; fetchCategories(); - } - }, [token]); - + }); const categories = Array.from( new Set(notices.map((notice) => notice.category)) @@ -35,13 +28,11 @@ export function CategorySection({notices, title}) { const getCount = (category) => notices.filter((notice) => notice.category === category).length; - console.log("CategoryMap:", categoryMap); - - if(!categoryMap) { - return null; + if (!categoryMap || Object.keys(categoryMap).length === 0) { + return null; } -return ( + return ( {title} cat.value === item); return ( - - - {categoryObj ? categoryObj.label : item} ({getCount(item)}) - - + + + {categoryObj ? categoryObj.label : item} ({getCount(item)}) + + ); }} /> ); - -} \ No newline at end of file +} diff --git a/ArtisanConnect/components/UserSection.jsx b/ArtisanConnect/components/UserSection.jsx index 69ea77e..7cd6c61 100644 --- a/ArtisanConnect/components/UserSection.jsx +++ b/ArtisanConnect/components/UserSection.jsx @@ -1,27 +1,27 @@ -import { View} from 'react-native'; -import { useEffect, useState } from 'react' -import { Heading } from '@/components/ui/heading'; -import { FlatList } from 'react-native'; -import axios from 'axios'; -import UserBlock from '@/components/UserBlock'; -import {useAuthStore} from "@/store/authStore"; +import { View } from "react-native"; +import { useEffect, useState } from "react"; +import { Heading } from "@/components/ui/heading"; +import { FlatList } from "react-native"; +import axios from "axios"; +import UserBlock from "@/components/UserBlock"; +import { useAuthStore } from "@/store/authStore"; - -export function UserSection({notices, title}) { - const token = useAuthStore((state) => state.token); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; - const [users, setUsers] = useState([]); +export function UserSection({ notices, title }) { + const token = useAuthStore((state) => state.token); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + const [users, setUsers] = useState([]); useEffect(() => { - if (token){ - axios.get('https://testowe.zikor.pl/api/v1/clients/get/all', { headers }) - .then(res => setUsers(res.data)) - .catch(() => setUsers([])); + if (token) { + axios + .get("https://hopp.zikor.pl/api/v1/clients/get/all", { headers }) + .then((res) => setUsers(res.data)) + .catch(() => setUsers([])); } }, [token]); - - const usersWithNoticeCount = users.map(user => { - const count = notices.filter(n => n.clientId === user.id).length; + + const usersWithNoticeCount = users.map((user) => { + const count = notices.filter((n) => n.clientId === user.id).length; return { ...user, noticeCount: count }; }); @@ -29,7 +29,7 @@ export function UserSection({notices, title}) { .sort((a, b) => b.noticeCount - a.noticeCount) .slice(0, 5); -return ( + return ( {title} { - return ( - - ); + return ; }} /> ); - -} \ No newline at end of file +} diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index 0bb66c6..db4a099 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -1,110 +1,135 @@ -import {create} from "zustand"; -import {createJSONStorage, persist} from "zustand/middleware"; +import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; import AsyncStorage from "@react-native-async-storage/async-storage"; import axios from "axios"; -const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export const useAuthStore = create( - persist( - (set) => ({ - user: null, - token: null, - isLoading: false, - error: null, + persist( + (set, get) => { + if (!axios.interceptors.response.handlers.length) { + axios.interceptors.response.use( + (response) => response, + (error) => { + if ( + (error.response && error.response.status === 401) || + error.response.status === 403 + ) { + set({ user: null, token: null, isLoading: false }); + delete axios.defaults.headers.common["Authorization"]; + } + return Promise.reject(error); + } + ); + } - signIn: async (email, password) => { - set({isLoading: true, error: null}); - try { - const response = await axios.post(`${API_URL}/auth/login`, { - email, - password - }); + return { + user: null, + token: null, + isLoading: false, + error: null, - const user = response.data.user; - const token = response.data.token; - set({user, token, isLoading: false}); - } catch (error) { - set({error: error.response?.data?.message || error.message, isLoading: false}); - throw error; - } - }, + signIn: async (email, password) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post(`${API_URL}/auth/login`, { + email, + password, + }); - signUp: async (userData) => { - set({isLoading: true, error: null}); - try { - console.log(userData); + const user = response.data.user; + const token = response.data.token; + set({ user, token, isLoading: false }); + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - const response = await axios.post(`${API_URL}/auth/register`, userData, { - headers: {'Content-Type': 'application/json'} - }); + signUp: async (userData) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post( + `${API_URL}/auth/register`, + userData, + { + headers: { "Content-Type": "application/json" }, + } + ); - console.log(response.data); + const user = response.data.user; + const token = response.data.token; + set({ user, token, isLoading: false }); + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + return user; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - const user = response.data.user; - const token = response.data.token; - set({user, token, isLoading: false}); + signInWithGoogle: async (googleToken) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post(`${API_URL}/auth/google`, { + token: googleToken, + }); - return user; - } catch (error) { - set({error: error.response?.data?.message || error.message, isLoading: false}); - throw error; - } - }, + const { user, token } = response.data; + set({ user, token, isLoading: false }); + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + return user; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - signInWithGoogle: async (googleToken) => { - set({isLoading: true, error: null}); - try { - const response = await axios.post(`${API_URL}/auth/google`, {token: googleToken}); + signOut: async () => { + try { + const { token } = get(); + const headers = token ? { Authorization: `Bearer ${token}` } : {}; + await axios.post(`${API_URL}/auth/logout`, {}, { headers }); + } catch (error) { + // console.error("Logout error:", error); + } finally { + delete axios.defaults.headers.common["Authorization"]; + set({ user: null, token: null, isLoading: false }); + } + }, - const {user, token} = response.data; - set({user, token, isLoading: false}); + checkAuth: async () => { + const { token } = get(); + if (!token) return null; - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - - return user; - } catch (error) { - set({error: error.response?.data?.message || error.message, isLoading: false}); - throw error; - } - }, - - signOut: async () => { - try { - const {token} = useAuthStore.getState(); - const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; - // Можно отправить запрос на бэкенд для инвалидации токена - await axios.post(`${API_URL}/auth/logout`, {}, { headers }); - } catch (error) { - console.error("Logout error:", error); - } finally { - delete axios.defaults.headers.common["Authorization"]; - set({user: null, token: null}); - } - }, - - checkAuth: async () => { - const {token} = useAuthStore.getState(); - if (!token) return null; - - set({isLoading: true}); - try { - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - - const response = await axios.get(`${API_URL}/auth/me`); - - set({user: response.data, isLoading: false}); - return response.data; - } catch (error) { - delete axios.defaults.headers.common["Authorization"]; - set({user: null, token: null, isLoading: false}); - return null; - } - }, - }), - { - name: "auth-storage", - storage: createJSONStorage(() => AsyncStorage), - } - ) -); \ No newline at end of file + set({ isLoading: true }); + try { + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + const response = await axios.get(`${API_URL}/auth/me`); + set({ user: response.data, isLoading: false }); + return response.data; + } catch (error) { + delete axios.defaults.headers.common["Authorization"]; + set({ user: null, token: null, isLoading: false }); + return null; + } + }, + }; + }, + { + name: "auth-storage", + storage: createJSONStorage(() => AsyncStorage), + } + ) +); From 612210a9442facbe341856f845e2a9c3ec399cd5 Mon Sep 17 00:00:00 2001 From: Patryk Date: Sat, 7 Jun 2025 23:15:22 +0200 Subject: [PATCH 16/18] fix wishlist in notice screen --- ArtisanConnect/app/notice/[id].jsx | 612 +++++++++++++++-------------- 1 file changed, 309 insertions(+), 303 deletions(-) diff --git a/ArtisanConnect/app/notice/[id].jsx b/ArtisanConnect/app/notice/[id].jsx index f95f3c6..f742029 100644 --- a/ArtisanConnect/app/notice/[id].jsx +++ b/ArtisanConnect/app/notice/[id].jsx @@ -1,331 +1,337 @@ -import {Link, Stack, useLocalSearchParams} from "expo-router"; -import {Box} from "@/components/ui/box"; -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 {Ionicons} from "@expo/vector-icons"; -import {ActivityIndicator, Dimensions, FlatList, View, TextInput} from "react-native"; -import {useEffect, useState, useRef} from "react"; -import {useNoticesStore} from "@/store/noticesStore"; -import {useWishlist} from "@/store/wishlistStore"; -import {Pressable, ScrollView} from "react-native"; -import {getUserById} from "@/api/client"; - -const {width} = Dimensions.get("window"); +import { Link, Stack, useLocalSearchParams } from "expo-router"; +import { Box } from "@/components/ui/box"; +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 { Ionicons } from "@expo/vector-icons"; +import { + ActivityIndicator, + Dimensions, + FlatList, + View, + TextInput, +} from "react-native"; +import { useEffect, useState, useRef } from "react"; +import { useNoticesStore } from "@/store/noticesStore"; +import { useWishlist } from "@/store/wishlistStore"; +import { Pressable, ScrollView } from "react-native"; +import { getUserById } from "@/api/client"; +const { width } = Dimensions.get("window"); export default function NoticeDetails() { - const {id} = useLocalSearchParams(); - const [images, setImages] = useState([]); - const [isImageLoading, setIsImageLoading] = useState(true); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [notice, setNotice] = useState(null); - const [user, setUser] = useState(null); - const [isUserLoading, setIsUserLoading] = useState(true); - const flatListRef = useRef(null); - const [currentIndex, setCurrentIndex] = useState(0); + const { id } = useLocalSearchParams(); + const [images, setImages] = useState([]); + const [isImageLoading, setIsImageLoading] = useState(true); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [notice, setNotice] = useState(null); + const [user, setUser] = useState(null); + const [isUserLoading, setIsUserLoading] = useState(true); + const flatListRef = useRef(null); + const [currentIndex, setCurrentIndex] = useState(0); + const [isMessageFormVisible, setIsMessageFormVisible] = useState(false); + const [message, setMessage] = useState(""); + const [Email, setEmail] = useState(""); + const handleSendMessage = () => { + console.log("Wiadomość do:", user?.email); + console.log("Email nadawcy:", Email); + console.log("Treść:", message); - const [isMessageFormVisible, setIsMessageFormVisible] = useState(false); - const [message, setMessage] = useState(''); - const [Email, setEmail] = useState(''); - const handleSendMessage = () => { - console.log('Wiadomość do:', user?.email); - console.log('Email nadawcy:', Email); - console.log('Treść:', message); + setIsMessageFormVisible(false); + setMessage(""); + setEmail(""); + }; - setIsMessageFormVisible(false); - setMessage(''); - setEmail(''); + const formatDate = (dateString) => { + const date = new Date(dateString); + return date.toLocaleDateString("pl-PL", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + }; + + const { getNoticeById, getAllImagesByNoticeId } = useNoticesStore(); + const toggleNoticeInWishlist = useWishlist( + (state) => state.toggleNoticeInWishlist + ); + + const isInWishlist = useWishlist((state) => + id ? state.wishlistNotices.some((item) => item.noticeId == id) : false + ); + const onViewableItemsChanged = useRef(({ viewableItems }) => { + if (viewableItems.length > 0) { + setCurrentIndex(viewableItems[0].index); + } + }).current; + + const viewabilityConfig = useRef({ + itemVisiblePercentThreshold: 70, + }).current; + + useEffect(() => { + const fetchNotice = async () => { + setIsLoading(true); + try { + const noticeData = getNoticeById(Number(id)); + if (noticeData) { + setNotice(noticeData); + setError(null); + } else { + setError(new Error(`Notice with ID ${id} not found.`)); + } + } catch (err) { + setError(err); + } finally { + setIsLoading(false); + } }; - const formatDate = (dateString) => { - const date = new Date(dateString); - return date.toLocaleDateString('pl-PL', { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }); + fetchNotice(); + }, [id]); + + useEffect(() => { + const fetchImage = async () => { + setIsImageLoading(true); + if (notice) { + try { + const fetchedImages = await getAllImagesByNoticeId(notice.noticeId); + setImages( + fetchedImages && fetchedImages.length > 0 + ? fetchedImages + : ["https://http.cat/404.jpg"] + ); + } catch (err) { + console.error("Error while loading images:", err); + setImage("https://http.cat/404.jpg"); + } finally { + setIsImageLoading(false); + } + } }; - const {getNoticeById, getAllImagesByNoticeId} = useNoticesStore(); - const addNoticeToWishlist = useWishlist((state) => state.addNoticeToWishlist); - const removeNoticeFromWishlist = useWishlist((state) => state.removeNoticeFromWishlist); - const isInWishlist = useWishlist((state) => - notice ? state.wishlistNotices.some((item) => item.noticeId === notice.noticeId) : false - ); - const onViewableItemsChanged = useRef(({ viewableItems }) => { - if (viewableItems.length > 0) { - setCurrentIndex(viewableItems[0].index); + if (notice) { + fetchImage(); + } + }, [notice]); + + useEffect(() => { + const fetchUser = async () => { + if (notice && notice.clientId) { + setIsUserLoading(true); + try { + const userData = await getUserById(notice.clientId); + setUser(userData); + } catch (err) { + console.error("Nie udało się pobrać danych użytkownika:", err); + } finally { + setIsUserLoading(false); } - }).current; + } + }; - const viewabilityConfig = useRef({ - itemVisiblePercentThreshold: 70, - }).current; + fetchUser(); + }, [notice]); + if (isLoading) { + return ; + } - useEffect(() => { - const fetchNotice = async () => { - setIsLoading(true); - try { - const noticeData = getNoticeById(Number(id)); - if (noticeData) { - setNotice(noticeData); - setError(null); - } else { - setError(new Error(`Notice with ID ${id} not found.`)); - } - } catch (err) { - setError(err); - } finally { - setIsLoading(false); - } - }; + if (error) { + return Błąd, spróbuj ponownie póżniej: {error.message}; + } - fetchNotice(); - }, [id]); + if (!notice) { + return Nie znaleziono ogłoszenia; + } - useEffect(() => { - const fetchImage = async () => { - setIsImageLoading(true); - if (notice) { - try { - const fetchedImages = await getAllImagesByNoticeId(notice.noticeId); - setImages(fetchedImages && fetchedImages.length > 0 ? fetchedImages : ["https://http.cat/404.jpg"]); - - } catch (err) { - console.error("Error while loading images:", err); - setImage("https://http.cat/404.jpg"); - } finally { - setIsImageLoading(false); - } - } - }; - - if (notice) { - fetchImage(); - } - }, [notice]); - - useEffect(() => { - const fetchUser = async () => { - if (notice && notice.clientId) { - setIsUserLoading(true); - try { - const userData = await getUserById(notice.clientId); - setUser(userData); - } catch (err) { - console.error("Nie udało się pobrać danych użytkownika:", err); - } finally { - setIsUserLoading(false); - } - } - }; - - fetchUser(); - }, [notice]); - - - if (isLoading) { - return ; - } - - if (error) { - return Błąd, spróbuj ponownie póżniej: {error.message}; - } - - if (!notice) { - return Nie znaleziono ogłoszenia; - } - - return ( - - - {isImageLoading ? ( - - - - ) : ( - - ( - - {`Zdjęcie - - )} - keyExtractor={(item, index) => index.toString()} - /> - - {images.length > 1 && ( - - {images.map((_, index) => ( - - ))} - - )} - + return ( + + + {isImageLoading ? ( + + + + ) : ( + + ( + + {`Zdjęcie + )} + keyExtractor={(item, index) => index.toString()} + /> - 1 && ( + + {images.map((_, index) => ( + + ))} + + )} + + )} + + + + + {formatDate(notice.publishDate)} + + + {notice.title} + + + + + Cena: + {notice.price} zł + + + { + toggleNoticeInWishlist(id); + }} > - - - {formatDate(notice.publishDate)} - - - {notice.title} - + + + + + + Kategoria:{" "} + {notice.category} + + + + Opis ogloszenia + + {notice.description} + + - - - Cena: - {notice.price} zł - - - { - if (isInWishlist) { - removeNoticeFromWishlist(notice.noticeId); - } else { - addNoticeToWishlist(notice); - } - }} - > - - - - - - Kategoria: {notice.category} - - - - - Opis ogloszenia - - - {notice.description} - + + Uzytkownik: + {isUserLoading ? ( + + ) : user ? ( + <> + + Zdjęcie profilowe - - - Uzytkownik: + + + {user.firstName} {user.lastName} + + + Email: {user.email} + + setIsMessageFormVisible(true)} + className="mt-3 bg-primary-500 py-2 px-4 rounded-md" + > + + Wyślij wiadomość - {isUserLoading ? ( - - ) : user ? ( - <> - - Zdjęcie profilowe - - - - - {user.firstName} {user.lastName} - - - Email: {user.email} - - setIsMessageFormVisible(true)} - className="mt-3 bg-primary-500 py-2 px-4 rounded-md" - > - Wyślij wiadomość - - - - Zobacz więcej ogłoszeń od {user.firstName} - - - - - ) : ( - Błąd podczas ładowania danych użytkownika - )} - + + + + Zobacz więcej ogłoszeń od {user.firstName} + + + + ) : ( + Błąd podczas ładowania danych użytkownika + )} + + + + {isMessageFormVisible && ( + + + + Wyślij wiadomość do {user?.firstName} + - - - {isMessageFormVisible && ( - - - Wyślij wiadomość do {user?.firstName} + Do: + + {user?.email || "Brak adresu e-mail"} + + Twój e-mail: + - Do: - - {user?.email || "Brak adresu e-mail"} - - Twój e-mail: - + - + + setIsMessageFormVisible(false)} + className="bg-gray-300 py-2 px-4 rounded-md" + > + Anuluj + - - setIsMessageFormVisible(false)} - className="bg-gray-300 py-2 px-4 rounded-md" - > - Anuluj - - - - Wyślij - - - - - )} - - - ); -} \ No newline at end of file + + Wyślij + + + + + )} + + ); +} From 717dd32543bd1ff7f173b59f89a1a397d45159f4 Mon Sep 17 00:00:00 2001 From: Patryk Date: Sun, 8 Jun 2025 09:21:04 +0200 Subject: [PATCH 17/18] refactor: clean up API_URL definition and improve error handling in authStore --- ArtisanConnect/api/notices.jsx | 61 ++++----- ArtisanConnect/store/authStore.jsx | 213 ++++++++++++++++------------- 2 files changed, 145 insertions(+), 129 deletions(-) diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx index 3a7bb9a..7a3601e 100644 --- a/ArtisanConnect/api/notices.jsx +++ b/ArtisanConnect/api/notices.jsx @@ -2,21 +2,22 @@ import axios from "axios"; import FormData from "form-data"; import { useAuthStore } from "@/store/authStore"; -const API_URL = "https://hopp.zikor.pl/api/v1"; +// const API_URL = "https://testowe.zikor.pl/api/v1"; +const API_URL = "https://hopp.zikor.pl/api/v1"; export async function listNotices() { const { token } = useAuthStore.getState(); const headers = token ? { Authorization: `Bearer ${token}` } : {}; - const response = await fetch(`${API_URL}/notices/get/all`, { - headers: headers - }); - const data = await response.json(); - if (!response.ok) { - throw new Error(response.toString()); - } - return data; + const response = await fetch(`${API_URL}/notices/get/all`, { + headers: headers, + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(response.toString()); + } + return data; } export async function getNoticeById(noticeId) { @@ -58,44 +59,40 @@ export async function getImageByNoticeId(noticeId) { const imageName = listResponse.data[0]; imageUrl = `${API_URL}/images/get/${imageName}`; - return imageUrl; - } catch (err) { - console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); - imageUrl = "https://http.cat/404.jpg"; - return imageUrl; - } + return imageUrl; + } catch (err) { + console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); + imageUrl = "https://http.cat/404.jpg"; + return imageUrl; + } } export async function getAllImagesByNoticeId(noticeId) { try { const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); - if (listResponse.data && listResponse.data.length > 0) { - return listResponse.data.map(imageName => - `${API_URL}/images/get/${imageName}` - ); - } - - return ["https://http.cat/404.jpg"]; - } catch (err) { - if(err.response.status === 404) { - console.info(`Ogłoszenie o id: ${noticeId} nie posiada zdjęć.`); - return ["https://http.cat/404.jpg"]; - } - console.warn(`Nie udało się pobrać listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); - return ["https://http.cat/404.jpg"]; + if (listResponse.data && listResponse.data.length > 0) { + return listResponse.data.map( + (imageName) => `${API_URL}/images/get/${imageName}` + ); } - // console.log(`Brak zdjęć dla ogłoszenia o id: ${noticeId}`); return ["https://http.cat/404.jpg"]; } catch (err) { - // console.log(`Błąd podczas pobierania listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); + if (err.response.status === 404) { + // console.info(`Ogłoszenie o id: ${noticeId} nie posiada zdjęć.`); + return ["https://http.cat/404.jpg"]; + } + console.warn( + `Nie udało się pobrać listy zdjęć dla ogłoszenia o id: ${noticeId}`, + err + ); return ["https://http.cat/404.jpg"]; } } export const uploadImage = async (noticeId, imageUri) => { - const formData = new FormData(); + const formData = new FormData(); const filename = imageUri.split("/").pop(); diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index 4d81fa3..7aa6f41 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -7,113 +7,132 @@ const API_URL = "https://hopp.zikor.pl/api/v1"; export const useAuthStore = create( persist( - (set) => ({ - user_id: null, - token: null, - isLoading: false, - error: null, - - signIn: async (email, password) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post(`${API_URL}/auth/login`, { - email, - password, - }); - - const user_id = response.data.user_id; - const token = response.data.token; - set({ user_id: user_id, token: token, isLoading: false }); - - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, - - signUp: async (userData) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post( - `${API_URL}/auth/register`, - userData, - { - headers: { "Content-Type": "application/json" }, + (set) => { + // Dodaj interceptor tylko raz + if (!axios.interceptors.response.handlers.length) { + axios.interceptors.response.use( + (response) => response, + (error) => { + if ( + (error.response && error.response.status === 401) || + error.response.status === 403 + ) { + set({ user_id: null, token: null, isLoading: false }); + delete axios.defaults.headers.common["Authorization"]; } - ); + return Promise.reject(error); + } + ); + } - const user_id = response.data.user_id; - const token = response.data.token; - set({ user_id: user_id, token: token, isLoading: false }); + return { + user_id: null, + token: null, + isLoading: false, + error: null, - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, + signIn: async (email, password) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post(`${API_URL}/auth/login`, { + email, + password, + }); - signInWithGoogle: async (googleToken) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post( - `${API_URL}/auth/google`, - { googleToken: googleToken }, - { - headers: { "Content-Type": "application/json" }, - } - ); - const user_id = response.data.user_id; - const token = response.data.token; - set({ user_id: user_id, token: token, isLoading: false }); + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - signOut: async () => { - try { - await axios.post(`${API_URL}/auth/logout`); - } catch (error) { - console.error("Logout error:", error); - } finally { - delete axios.defaults.headers.common["Authorization"]; - set({ user_id: null, token: null }); - } - }, + signUp: async (userData) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post( + `${API_URL}/auth/register`, + userData, + { + headers: { "Content-Type": "application/json" }, + } + ); - checkAuth: async () => { - const { token } = useAuthStore.getState(); - if (!token) return null; + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); - set({ isLoading: true }); - try { - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - const response = await axios.get(`${API_URL}/auth/me`); + signInWithGoogle: async (googleToken) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post( + `${API_URL}/auth/google`, + { googleToken: googleToken }, + { + headers: { "Content-Type": "application/json" }, + } + ); + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); - set({ user_id: response.data, isLoading: false }); - return response.data; - } catch (error) { - delete axios.defaults.headers.common["Authorization"]; - set({ user_id: null, token: null, isLoading: false }); - return null; - } - }, - }), + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, + + signOut: async () => { + try { + await axios.post(`${API_URL}/auth/logout`); + } catch (error) { + console.error("Logout error:", error); + } finally { + delete axios.defaults.headers.common["Authorization"]; + set({ user_id: null, token: null }); + } + }, + + checkAuth: async () => { + const { token } = useAuthStore.getState(); + if (!token) return null; + + set({ isLoading: true }); + try { + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + + const response = await axios.get(`${API_URL}/auth/me`); + + set({ user_id: response.data, isLoading: false }); + return response.data; + } catch (error) { + delete axios.defaults.headers.common["Authorization"]; + set({ user_id: null, token: null, isLoading: false }); + return null; + } + }, + }; + }, { name: "auth-storage", storage: createJSONStorage(() => AsyncStorage), From c39f9c383e415b73630231061d3a0b559783246b Mon Sep 17 00:00:00 2001 From: Patryk Date: Sun, 8 Jun 2025 09:37:50 +0200 Subject: [PATCH 18/18] fix account --- .../app/(tabs)/dashboard/account.jsx | 22 ++++++------ ArtisanConnect/app/(tabs)/wishlist.jsx | 4 ++- ArtisanConnect/store/authStore.jsx | 34 +++++++++---------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/ArtisanConnect/app/(tabs)/dashboard/account.jsx b/ArtisanConnect/app/(tabs)/dashboard/account.jsx index 1b770e5..d30c302 100644 --- a/ArtisanConnect/app/(tabs)/dashboard/account.jsx +++ b/ArtisanConnect/app/(tabs)/dashboard/account.jsx @@ -55,13 +55,6 @@ export default function Account() { {user.firstName} {user.lastName} - - console.log("Edytuj dane użytkownika")} - > - Edytuj profil - @@ -71,6 +64,13 @@ export default function Account() { E-mail {user.email || "brak danych"} + + console.log("Edytuj dane użytkownika")} + > + Edytuj profil + @@ -91,15 +91,15 @@ export default function Account() { - + {/* Ustawienia powiadomień - + */} - + {/* Wyloguj się - + */} ); } diff --git a/ArtisanConnect/app/(tabs)/wishlist.jsx b/ArtisanConnect/app/(tabs)/wishlist.jsx index d6595c8..60c8f5d 100644 --- a/ArtisanConnect/app/(tabs)/wishlist.jsx +++ b/ArtisanConnect/app/(tabs)/wishlist.jsx @@ -7,12 +7,14 @@ import { Text } from "@/components/ui/text"; import { useEffect } from "react"; export default function Wishlist() { - const wishlistNotices = useWishlist((state) => state.wishlistNotices); + const wishlistNotices = useWishlist((state) => state.wishlistNotices); const fetchWishlist = useWishlist((state) => state.fetchWishlist); useEffect(() => { fetchWishlist(); }, []); + + // console.log("Wishlist notices:", wishlistNotices); if (wishlistNotices.length === 0) { return ( diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index 7aa6f41..a5d14d3 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -7,7 +7,7 @@ const API_URL = "https://hopp.zikor.pl/api/v1"; export const useAuthStore = create( persist( - (set) => { + (set, get) => { // Dodaj interceptor tylko raz if (!axios.interceptors.response.handlers.length) { axios.interceptors.response.use( @@ -17,7 +17,7 @@ export const useAuthStore = create( (error.response && error.response.status === 401) || error.response.status === 403 ) { - set({ user_id: null, token: null, isLoading: false }); + set({ user: null, token: null, isLoading: false }); delete axios.defaults.headers.common["Authorization"]; } return Promise.reject(error); @@ -113,24 +113,24 @@ export const useAuthStore = create( } }, - checkAuth: async () => { - const { token } = useAuthStore.getState(); - if (!token) return null; + // checkAuth: async () => { + // const { token } = useAuthStore.getState(); + // if (!token) return null; - set({ isLoading: true }); - try { - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + // set({ isLoading: true }); + // try { + // axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - const response = await axios.get(`${API_URL}/auth/me`); + // const response = await axios.get(`${API_URL}/auth/me`); - set({ user_id: response.data, isLoading: false }); - return response.data; - } catch (error) { - delete axios.defaults.headers.common["Authorization"]; - set({ user_id: null, token: null, isLoading: false }); - return null; - } - }, + // set({ user_id: response.data, isLoading: false }); + // return response.data; + // } catch (error) { + // delete axios.defaults.headers.common["Authorization"]; + // set({ user_id: null, token: null, isLoading: false }); + // return null; + // } + // }, }; }, {