Merge remote-tracking branch 'origin/notice_apperance' into integrateWithAuth
This commit is contained in:
13
ArtisanConnect/api/client.jsx
Normal file
13
ArtisanConnect/api/client.jsx
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}zł
|
||||
|
||||
<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} zł
|
||||
</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>
|
||||
);
|
||||
}
|
||||
69
ArtisanConnect/app/user/[userId].jsx
Normal file
69
ArtisanConnect/app/user/[userId].jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user