Merge remote-tracking branch 'origin/notice_apperance' into integrateWithAuth

This commit is contained in:
Patryk
2025-06-07 12:54:19 +02:00
5 changed files with 474 additions and 34 deletions

View File

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

View File

@@ -1,16 +1,105 @@
import { Link } from "expo-router";
import { Pressable } from "react-native";
import { Box } from "@/components/ui/box";
import { Text } from "@/components/ui/text";
import { View } from "react-native";
import { Button, ButtonText } from "@gluestack-ui/themed";
import { useAuthStore } from "@/store/authStore";
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";
import { HStack } from "@gluestack-ui/themed";
export default function User() {
const signOut = useAuthStore((state) => state.signOut);
return (<View>
<Text>Użytkownik</Text>
<Button onPress={signOut}>
<ButtonText>Wyloguj się</ButtonText>
</Button>
</View>
)
export default function Account() {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const currentUserId = 2; // 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 <ActivityIndicator />;
}
if (!user) {
return <Text>Nie udało się pobrać danych użytkownika.</Text>;
}
return (
<VStack className="bg-gray-50 flex-1">
<Box className="bg-white pb-6 shadow-sm">
<Box className="items-center pt-6 mb-4">
<Image
source={{
uri:
user.profileImage ||
"https://th.bing.com/th/id/OIP.3coo_N8sieled8QNroQmkgHaHa?rs=1&pid=ImgDetMain",
}}
className="h-24 w-24 rounded-full border-4 border-white shadow-md"
alt="Zdjęcie profilowe"
/>
</Box>
<Text className="text-2xl font-bold text-center mb-1">
{user.firstName} {user.lastName}
</Text>
<Pressable
className="border border-[#002f34] rounded-md py-2 px-4 self-center"
onPress={() => console.log("Edytuj dane użytkownika")}
>
<Text className="text-[#002f34] font-medium">Edytuj profil</Text>
</Pressable>
</Box>
<Box className="bg-white mt-4 p-5 border-t border-b border-gray-200">
<Text className="font-bold text-lg mb-3">Moje dane</Text>
<HStack className="mb-3">
<Text className="text-gray-600 w-24">E-mail</Text>
<Text className="flex-1">{user.email || "brak danych"}</Text>
</HStack>
</Box>
<Box className="bg-white mt-4 p-5">
<Text className="font-bold text-lg mb-3">Moje konto</Text>
<Link href="/dashboard/userNotices" asChild>
<Pressable className="py-3 flex-row items-center border-b border-gray-100">
<Text className="text-lg flex-1">Moje ogłoszenia</Text>
<Text></Text>
</Pressable>
</Link>
{/*Tak dodałem, można zmienić na coś innego*/}
<Link href="/dashboard/userPaymentHistory" asChild>
<Pressable className="py-3 flex-row items-center border-b border-gray-100">
<Text className="text-lg flex-1">Historia płatności</Text>
<Text></Text>
</Pressable>
</Link>
<Pressable className="py-3 flex-row items-center">
<Text className="text-lg flex-1">Ustawienia powiadomień</Text>
<Text></Text>
</Pressable>
</Box>
<Pressable className="mt-8 mx-5 p-4 bg-white rounded-md items-center shadow-sm">
<Text className="text-red-500 font-medium">Wyloguj się</Text>
</Pressable>
</VStack>
);
}

View File

@@ -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 <Text>Użytkownik</Text>;
}
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 <ActivityIndicator />;
}
return (
<VStack className="p-4">
<Text className="text-2xl font-bold mb-4">Moje ogłoszenia</Text>
{userNotices.length > 0 ? (
<FlatList
data={userNotices}
numColumns={2}
columnWrapperStyle={{ marginBottom: 10, justifyContent: "space-between" }}
renderItem={({ item }) => (
<Box className="flex-1">
<NoticeCard notice={item} />
<Box className="flex-row justify-between mt-2">
<Button
title="Promuj"
onPress={() => {
// TODO: Implementacja promocji ogłoszenia
console.log(`Promuj ogłoszenie ${item.noticeId}`);
}}
className="bg-primary-500 py-2 px-4 rounded-md"
>
</Button>
<Button
title="Podbij"
onPress={() => {
// TODO: Implementacja podbicia ogłoszenia
console.log(`Podbij ogłoszenie ${item.noticeId}`);
}}
className="bg-primary-500 py-2 px-4 rounded-md"
>
</Button>
</Box>
</Box>
)}
keyExtractor={(item) => item.noticeId.toString()}
/>
) : (
<Text>Nie masz żadnych ogłoszeń.</Text>
)}
</VStack>
);
}

View File

@@ -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,19 +6,50 @@ 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, TextInput} 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");
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 [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 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);
@@ -26,6 +57,16 @@ 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 () => {
@@ -53,8 +94,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");
@@ -69,6 +111,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 <ActivityIndicator/>;
}
@@ -93,24 +154,60 @@ export default function NoticeDetails() {
<ActivityIndicator size="large" color="#3b82f6" />
</Box>
) : (
<Image
source={{
uri: image,
}}
className="h-auto w-full rounded-md aspect-[1/1]"
alt="image"
resizeMode="cover"
/>
<Box className="sticky top-0 z-10 bg-white">
<FlatList
ref={flatListRef}
data={images}
horizontal
snapToAlignment="center"
decelerationRate="fast"
showsHorizontalScrollIndicator={false}
pagingEnabled
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
renderItem={({ item, index }) => (
<View style={{ width: width }}>
<Image
source={{ uri: item }}
className="h-auto w-full rounded-md aspect-square"
alt={`Zdjęcie ${index + 1}`}
resizeMode="contain"
/>
</View>
)}
keyExtractor={(item, index) => index.toString()}
/>
{images.length > 1 && (
<Box className="flex-row justify-center mt-2">
{images.map((_, index) => (
<Box
key={index}
className={`w-2 h-2 rounded-full mx-1 ${index === currentIndex ? 'bg-primary-500' : 'bg-gray-300'}`}
/>
))}
</Box>
)}
</Box>
)}
<ScrollView
showsVerticalScrollIndicator={false}
>
<VStack className="p-2">
<Text className="text-sm font-normal mb-2 text-typography-700">
{formatDate(notice.publishDate)}
</Text>
<Text className="text-2xl text-gray-950 font-bold mb-2 text-center bg-gray-50 rounded-md p-2">
{notice.title}
</Text>
<Box className="flex-row items-center">
<Heading size="md" className="flex-1">
{notice.price}
<Box className="flex-row items-center bg-gray-50 rounded-md p-2">
<Heading size="md" className="flex-1 text-xl text-gray-950">
<Text className="text-sm text-typography-500">Cena: </Text>
{notice.price}
</Heading>
<Pressable
onPress={() => {
if (isInWishlist) {
@@ -127,7 +224,108 @@ export default function NoticeDetails() {
/>
</Pressable>
</Box>
<Box className="mt-4 bg-gray-50 p-3 rounded-lg shadow-sm">
<Text className="text-sm text-typography-500">
Kategoria: <Text className="font-bold text-gray-950">{notice.category}</Text>
</Text>
</Box>
<Box className="mt-4 bg-gray-50 p-3 rounded-lg shadow-sm">
<Text className="text-2xl text-gray-950">
Opis ogloszenia
</Text>
<Text className="text-sm text-typography-700">
{notice.description}
</Text>
</Box>
<Box className="mt-4 bg-gray-50 p-3 rounded-lg shadow-sm">
<Text className="text-sm text-typography-500">
Uzytkownik:
</Text>
{isUserLoading ? (
<ActivityIndicator />
) : user ? (
<>
<Box className="mr-4">
<Image
source={{ uri: user.profileImage || "https://th.bing.com/th/id/OIP.3coo_N8sieled8QNroQmkgHaHa?rs=1&pid=ImgDetMain" }} // Domyślny obraz, jeśli brak zdjęcia profilowego
className="h-12 w-12 rounded-full"
alt="Zdjęcie profilowe"
/>
</Box>
<Box className="flex-1">
<Text className="text-xl font-bold text-gray-950">
{user.firstName} {user.lastName}
</Text>
<Text className="text-sm text-typography-700">
Email: {user.email}
</Text>
<Pressable
onPress={() => setIsMessageFormVisible(true)}
className="mt-3 bg-primary-500 py-2 px-4 rounded-md"
>
<Text className="text-white text-center font-bold">Wyślij wiadomość</Text>
</Pressable>
<Link href={`/user/${notice.clientId}`}>
<Text className="text-xl p-3 font-bold text-center text-typography-700 mt-3">
Zobacz więcej ogłoszeń od {user.firstName}
</Text>
</Link>
</Box>
</>
) : (
<Text>Błąd podczas ładowania danych użytkownika</Text>
)}
</Box>
</VStack>
</ScrollView>
{isMessageFormVisible && (
<View className="absolute inset-0 bg-black bg-opacity-50 justify-center items-center z-20">
<View className="bg-white p-4 rounded-lg w-4/5">
<Text className="text-lg font-bold mb-4">Wyślij wiadomość do {user?.firstName}</Text>
<Text className="text-sm font-medium mb-1">Do:</Text>
<Text className="bg-gray-100 p-3 rounded text-gray-500">
{user?.email || "Brak adresu e-mail"}
</Text>
<Text className="text-sm font-medium mb-1">Twój e-mail:</Text>
<TextInput
className="border border-gray-300 p-2 rounded"
placeholder="Wpisz swój adres e-mail"
value={Email}
onChangeText={setEmail}
/>
<TextInput
className="border border-gray-300 rounded-md p-2 mb-4 h-32 text-left"
multiline
numberOfLines={4}
placeholder="Napisz swoją wiadomość..."
value={message}
onChangeText={setMessage}
/>
<View className="flex-row justify-end space-x-2">
<Pressable
onPress={() => setIsMessageFormVisible(false)}
className="bg-gray-300 py-2 px-4 rounded-md"
>
<Text className="text-gray-800">Anuluj</Text>
</Pressable>
<Pressable
onPress={handleSendMessage}
className="bg-primary-500 py-2 px-4 rounded-md">
<Text className="text-white">Wyślij</Text>
</Pressable>
</View>
</View>
</View>
)}
</Card>
);
}

View File

@@ -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 <ActivityIndicator />;
}
if (!user) {
return <Text>Nie znaleziono użytkownika</Text>;
}
const userNotices = notices.filter(notice => notice.clientId === Number(userId));
return (
<VStack className="p-4">
<Box className="flex-row items-center mb-4">
<Image
source={{ uri: user.profileImage || "https://th.bing.com/th/id/OIP.3coo_N8sieled8QNroQmkgHaHa?rs=1&pid=ImgDetMain" }}
className="h-16 w-16 rounded-full mr-4"
alt="Zdjęcie profilowe"
/>
<Heading size="lg">
{user.firstName} {user.lastName}
</Heading>
</Box>
{userNotices.length > 0 ? (
<FlatList
data={userNotices}
numColumns={2}
columnWrapperStyle={{ marginBottom: 10, justifyContent: "space-between" }}
renderItem={({ item }) => <NoticeCard notice={item} />}
keyExtractor={(item) => item.noticeId.toString()}
/>
) : (
<Text>Ten użytkownik nie ma żadnych ogłoszeń.</Text>
)}
</VStack>
);
}