API
User search: Fix: - api get all user - searching by username Portofolio: Fix: - dot button hanya muncul jika user yang memiliki portofolio tersebut yang melihat Profile: Fix: - dot button muncul hanya untuk user yang memiliki akunnya ### No Issue
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { DrawerCustom, Spacing, StackCustom } from "@/components";
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { DrawerCustom, LoaderCustom, Spacing, StackCustom } from "@/components";
|
||||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
@@ -10,18 +11,21 @@ import { drawerItemsPortofolio } from "@/screens/Portofolio/ListPage";
|
|||||||
import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
|
import Portofolio_MenuDrawerSection from "@/screens/Portofolio/MenuDrawer";
|
||||||
import Portofolio_SocialMediaSection from "@/screens/Portofolio/SocialMediaSection";
|
import Portofolio_SocialMediaSection from "@/screens/Portofolio/SocialMediaSection";
|
||||||
import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio";
|
import { apiGetOnePortofolio } from "@/service/api-client/api-portofolio";
|
||||||
|
import { apiUser } from "@/service/api-client/api-user";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
import { Stack, useFocusEffect, useLocalSearchParams } from "expo-router";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { TouchableOpacity } from "react-native";
|
import { TouchableOpacity } from "react-native";
|
||||||
|
|
||||||
export default function Portofolio() {
|
export default function Portofolio() {
|
||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
const { user } = useAuth();
|
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
const [isLoadingDelete, setIsLoadingDelete] = useState(false);
|
const [isLoadingDelete, setIsLoadingDelete] = useState(false);
|
||||||
const [data, setData] = useState<any>();
|
const [data, setData] = useState<any>();
|
||||||
|
const [profileId, setProfileId] = useState<any>();
|
||||||
|
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
setIsDrawerOpen(true);
|
setIsDrawerOpen(true);
|
||||||
@@ -33,24 +37,27 @@ export default function Portofolio() {
|
|||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadData(id as string);
|
onLoadData(id as string);
|
||||||
|
onLoadUserByToken();
|
||||||
}, [id])
|
}, [id])
|
||||||
);
|
);
|
||||||
|
|
||||||
async function onLoadData(id: string) {
|
async function onLoadData(id: string) {
|
||||||
const response = await apiGetOnePortofolio({ id: id });
|
const response = await apiGetOnePortofolio({ id: id });
|
||||||
// console.log(
|
console.log(
|
||||||
// "Response portofolio >>",
|
"[PROFILE ID]>>",
|
||||||
// JSON.stringify(response.data, null, 2)
|
JSON.stringify(response.data.Profile.id, null, 2)
|
||||||
// );
|
);
|
||||||
|
|
||||||
setData(response.data);
|
setData(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = user?.id;
|
const onLoadUserByToken = async () => {
|
||||||
const userLoginId = data?.Profile?.userId
|
const response = await apiUser(user?.id as string);
|
||||||
|
console.log(
|
||||||
console.log("User ID >>", userId);
|
"[PROFILE LOGIN]>>",
|
||||||
console.log("User Login ID >>", userLoginId);
|
JSON.stringify(response.data?.Profile.id, null, 2)
|
||||||
|
);
|
||||||
|
setProfileId(response?.data?.Profile?.id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -59,32 +66,41 @@ export default function Portofolio() {
|
|||||||
options={{
|
options={{
|
||||||
title: "Portofolio",
|
title: "Portofolio",
|
||||||
headerLeft: () => <LeftButtonCustom />,
|
headerLeft: () => <LeftButtonCustom />,
|
||||||
headerRight: () => (
|
headerRight: () =>
|
||||||
<TouchableOpacity onPress={openDrawer}>
|
data?.Profile?.id !== profileId ? null : (
|
||||||
<Ionicons
|
<TouchableOpacity onPress={openDrawer}>
|
||||||
name="ellipsis-vertical"
|
<Ionicons
|
||||||
size={20}
|
name="ellipsis-vertical"
|
||||||
color={MainColor.yellow}
|
size={20}
|
||||||
/>
|
color={MainColor.yellow}
|
||||||
</TouchableOpacity>
|
/>
|
||||||
),
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
headerStyle: GStyles.headerStyle,
|
headerStyle: GStyles.headerStyle,
|
||||||
headerTitleStyle: GStyles.headerTitleStyle,
|
headerTitleStyle: GStyles.headerTitleStyle,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ViewWrapper>
|
<ViewWrapper>
|
||||||
{/* <PorfofolioSection setShowDeleteAlert={setDeleteAlert} data={data} /> */}
|
{!data || !profileId ? (
|
||||||
<StackCustom>
|
<LoaderCustom />
|
||||||
<Portofolio_Data data={data} listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any []} />
|
) : (
|
||||||
<Portofolio_BusinessLocation />
|
<StackCustom>
|
||||||
<Portofolio_SocialMediaSection data={data?.Portofolio_MediaSosial} />
|
<Portofolio_Data
|
||||||
<Portofolio_ButtonDelete
|
data={data}
|
||||||
id={id as string}
|
listSubBidang={data?.Portofolio_BidangDanSubBidangBisnis as any[]}
|
||||||
isLoadingDelete={isLoadingDelete}
|
/>
|
||||||
setIsLoadingDelete={setIsLoadingDelete}
|
<Portofolio_BusinessLocation />
|
||||||
/>
|
<Portofolio_SocialMediaSection
|
||||||
<Spacing />
|
data={data?.Portofolio_MediaSosial}
|
||||||
</StackCustom>
|
/>
|
||||||
|
<Portofolio_ButtonDelete
|
||||||
|
id={id as string}
|
||||||
|
isLoadingDelete={isLoadingDelete}
|
||||||
|
setIsLoadingDelete={setIsLoadingDelete}
|
||||||
|
/>
|
||||||
|
<Spacing />
|
||||||
|
</StackCustom>
|
||||||
|
)}
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
|
|
||||||
{/* Drawer Komponen Eksternal */}
|
{/* Drawer Komponen Eksternal */}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
|
import { LoaderCustom } from "@/components";
|
||||||
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
import ViewWrapper from "@/components/_ShareComponent/ViewWrapper";
|
||||||
import LeftButtonCustom from "@/components/Button/BackButton";
|
import LeftButtonCustom from "@/components/Button/BackButton";
|
||||||
import DrawerCustom from "@/components/Drawer/DrawerCustom";
|
import DrawerCustom from "@/components/Drawer/DrawerCustom";
|
||||||
@@ -9,6 +11,7 @@ import Profile_PortofolioSection from "@/screens/Profile/PortofolioSection";
|
|||||||
import ProfileSection from "@/screens/Profile/ProfileSection";
|
import ProfileSection from "@/screens/Profile/ProfileSection";
|
||||||
import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
|
import { apiGetPortofolio } from "@/service/api-client/api-portofolio";
|
||||||
import { apiProfile } from "@/service/api-client/api-profile";
|
import { apiProfile } from "@/service/api-client/api-profile";
|
||||||
|
import { apiUser } from "@/service/api-client/api-user";
|
||||||
import { GStyles } from "@/styles/global-styles";
|
import { GStyles } from "@/styles/global-styles";
|
||||||
import { IProfile } from "@/types/Type-Profile";
|
import { IProfile } from "@/types/Type-Profile";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
@@ -20,9 +23,10 @@ export default function Profile() {
|
|||||||
const { id } = useLocalSearchParams();
|
const { id } = useLocalSearchParams();
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
const [data, setData] = useState<IProfile>();
|
const [data, setData] = useState<IProfile>();
|
||||||
const [dataPortofolio, setDataPortofolio] = useState<any[]>();
|
const [dataToken, setDataToken] = useState<IProfile>();
|
||||||
|
const [listPortofolio, setListPortofolio] = useState<any[]>();
|
||||||
|
|
||||||
const { logout, isAdmin } = useAuth();
|
const { logout, isAdmin, user } = useAuth();
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
setIsDrawerOpen(true);
|
setIsDrawerOpen(true);
|
||||||
@@ -36,14 +40,28 @@ export default function Profile() {
|
|||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
onLoadData(id as string);
|
onLoadData(id as string);
|
||||||
onLoadPortofolio(id as string);
|
onLoadPortofolio(id as string);
|
||||||
|
onLoadUserByToken();
|
||||||
|
isUserCheck();
|
||||||
}, [id])
|
}, [id])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isUserCheck = () => {
|
||||||
|
const userId = id;
|
||||||
|
const userLoginId = dataToken?.id;
|
||||||
|
|
||||||
|
return userId === userLoginId;
|
||||||
|
};
|
||||||
|
|
||||||
const onLoadData = async (id: string) => {
|
const onLoadData = async (id: string) => {
|
||||||
const response = await apiProfile({ id: id });
|
const response = await apiProfile({ id: id });
|
||||||
setData(response.data);
|
setData(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onLoadUserByToken = async () => {
|
||||||
|
const response = await apiUser(user?.id as string);
|
||||||
|
setDataToken(response?.data?.Profile);
|
||||||
|
};
|
||||||
|
|
||||||
const onLoadPortofolio = async (id: string) => {
|
const onLoadPortofolio = async (id: string) => {
|
||||||
const response = await apiGetPortofolio({ id: id });
|
const response = await apiGetPortofolio({ id: id });
|
||||||
const lastTwoByDate = response.data
|
const lastTwoByDate = response.data
|
||||||
@@ -52,7 +70,7 @@ export default function Profile() {
|
|||||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||||
) // urut desc
|
) // urut desc
|
||||||
.slice(0, 2);
|
.slice(0, 2);
|
||||||
setDataPortofolio(lastTwoByDate);
|
setListPortofolio(lastTwoByDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -61,27 +79,34 @@ export default function Profile() {
|
|||||||
options={{
|
options={{
|
||||||
title: "Profile",
|
title: "Profile",
|
||||||
headerLeft: () => <LeftButtonCustom />,
|
headerLeft: () => <LeftButtonCustom />,
|
||||||
headerRight: () => (
|
headerRight: () =>
|
||||||
<TouchableOpacity onPress={openDrawer}>
|
isUserCheck() && (
|
||||||
<Ionicons
|
<TouchableOpacity onPress={openDrawer}>
|
||||||
name="ellipsis-vertical"
|
<Ionicons
|
||||||
size={20}
|
name="ellipsis-vertical"
|
||||||
color={MainColor.yellow}
|
size={20}
|
||||||
/>
|
color={MainColor.yellow}
|
||||||
</TouchableOpacity>
|
/>
|
||||||
),
|
</TouchableOpacity>
|
||||||
|
),
|
||||||
headerStyle: GStyles.headerStyle,
|
headerStyle: GStyles.headerStyle,
|
||||||
headerTitleStyle: GStyles.headerTitleStyle,
|
headerTitleStyle: GStyles.headerTitleStyle,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* Main View */}
|
{/* Main View */}
|
||||||
<ViewWrapper>
|
<ViewWrapper>
|
||||||
<ProfileSection data={data as any} />
|
{!data || !dataToken ? (
|
||||||
|
<LoaderCustom />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ProfileSection data={data as any} />
|
||||||
|
|
||||||
<Profile_PortofolioSection
|
<Profile_PortofolioSection
|
||||||
data={dataPortofolio as any}
|
data={listPortofolio as any}
|
||||||
profileId={id as string}
|
profileId={id as string}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
|
|
||||||
{/* Drawer Komponen Eksternal */}
|
{/* Drawer Komponen Eksternal */}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
AvatarCustom,
|
AvatarComp,
|
||||||
ClickableCustom,
|
ClickableCustom,
|
||||||
Grid,
|
Grid,
|
||||||
Spacing,
|
Spacing,
|
||||||
@@ -10,39 +10,42 @@ import {
|
|||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { MainColor } from "@/constants/color-palet";
|
import { MainColor } from "@/constants/color-palet";
|
||||||
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
import { ICON_SIZE_SMALL } from "@/constants/constans-value";
|
||||||
|
import { apiAllUser } from "@/service/api-client/api-user";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
|
import _ from "lodash";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function UserSearch() {
|
export default function UserSearch() {
|
||||||
function generateRandomPhoneNumber(index: number) {
|
const [data, setData] = useState<any[]>([]);
|
||||||
let prefix;
|
const [search, setSearch] = useState<string>("");
|
||||||
|
|
||||||
// Menentukan prefix berdasarkan index genap atau ganjil
|
useEffect(() => {
|
||||||
if (index % 2 === 0) {
|
onLoadData(search);
|
||||||
const evenPrefixes = ["6288", "6289", "6281"];
|
}, [search]);
|
||||||
prefix = evenPrefixes[Math.floor(Math.random() * evenPrefixes.length)];
|
|
||||||
} else {
|
const onLoadData = async (search: string) => {
|
||||||
const oddPrefixes = ["6285", "6283"];
|
try {
|
||||||
prefix = oddPrefixes[Math.floor(Math.random() * oddPrefixes.length)];
|
const response = await apiAllUser({ search: search });
|
||||||
|
console.log("[DATA USER] >", JSON.stringify(response.data, null, 2));
|
||||||
|
setData(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error fetching data", error);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Menghitung panjang sisa nomor acak (antara 10 - 12 digit)
|
const handleSearch = (search: string) => {
|
||||||
const remainingLength = Math.floor(Math.random() * 3) + 10; // 10, 11, atau 12
|
setSearch(search);
|
||||||
|
onLoadData(search);
|
||||||
|
};
|
||||||
|
|
||||||
// Membuat sisa nomor acak
|
|
||||||
let randomNumber = "";
|
|
||||||
for (let i = 0; i < remainingLength; i++) {
|
|
||||||
randomNumber += Math.floor(Math.random() * 10); // Digit acak antara 0-9
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menggabungkan prefix dan sisa nomor
|
|
||||||
return prefix + randomNumber;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewWrapper
|
<ViewWrapper
|
||||||
headerComponent={
|
headerComponent={
|
||||||
<TextInputCustom
|
<TextInputCustom
|
||||||
|
value={search}
|
||||||
|
onChangeText={handleSearch}
|
||||||
iconLeft={
|
iconLeft={
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name="search"
|
name="search"
|
||||||
@@ -57,41 +60,46 @@ export default function UserSearch() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StackCustom>
|
<StackCustom>
|
||||||
{Array.from({ length: 20 }).map((e, index) => {
|
{!_.isEmpty(data) ? (
|
||||||
return (
|
data?.map((e, index) => {
|
||||||
<Grid key={index}>
|
return (
|
||||||
<Grid.Col span={2}>
|
<ClickableCustom
|
||||||
<AvatarCustom href={`/profile/${index}`}/>
|
key={index}
|
||||||
</Grid.Col>
|
onPress={() => {
|
||||||
<Grid.Col span={9}>
|
console.log("Ke Profile");
|
||||||
<TextCustom size="large">Nama user {index}</TextCustom>
|
router.push(`/profile/${e?.Profile?.id}`);
|
||||||
<TextCustom size="small">
|
|
||||||
+{generateRandomPhoneNumber(index)}
|
|
||||||
</TextCustom>
|
|
||||||
</Grid.Col>
|
|
||||||
<Grid.Col
|
|
||||||
span={1}
|
|
||||||
style={{
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "flex-end",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClickableCustom
|
<Grid>
|
||||||
onPress={() => {
|
<Grid.Col span={2}>
|
||||||
console.log("Ke Profile");
|
<AvatarComp fileId={e?.Profile?.imageId} size="base" />
|
||||||
router.push(`/profile/${index}`);
|
</Grid.Col>
|
||||||
}}
|
<Grid.Col span={9}>
|
||||||
>
|
<StackCustom gap={"sm"}>
|
||||||
<Ionicons
|
<TextCustom size="large">{e?.username}</TextCustom>
|
||||||
name="chevron-forward"
|
<TextCustom size="small">+{e?.nomor}</TextCustom>
|
||||||
size={ICON_SIZE_SMALL}
|
</StackCustom>
|
||||||
color={MainColor.white}
|
</Grid.Col>
|
||||||
/>
|
<Grid.Col
|
||||||
</ClickableCustom>
|
span={1}
|
||||||
</Grid.Col>
|
style={{
|
||||||
</Grid>
|
justifyContent: "center",
|
||||||
);
|
alignItems: "flex-end",
|
||||||
})}
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={ICON_SIZE_SMALL}
|
||||||
|
color={MainColor.white}
|
||||||
|
/>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
</ClickableCustom>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<TextCustom align="center">Tidak ditemukan</TextCustom>
|
||||||
|
)}
|
||||||
</StackCustom>
|
</StackCustom>
|
||||||
<Spacing height={50} />
|
<Spacing height={50} />
|
||||||
</ViewWrapper>
|
</ViewWrapper>
|
||||||
|
|||||||
8
components/Loader/LoaderCustom.tsx
Normal file
8
components/Loader/LoaderCustom.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { MainColor } from "@/constants/color-palet";
|
||||||
|
import { ActivityIndicator } from "react-native";
|
||||||
|
|
||||||
|
export default function LoaderCustom({ size }: { size?: "small" | "large" }) {
|
||||||
|
return (
|
||||||
|
<ActivityIndicator size={size ? size : "small"} color={MainColor.yellow} />
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -61,6 +61,8 @@ import DummyLandscapeImage from "./_ShareComponent/DummyLandscapeImage";
|
|||||||
import GridComponentView from "./_ShareComponent/GridSectionView";
|
import GridComponentView from "./_ShareComponent/GridSectionView";
|
||||||
// Progress
|
// Progress
|
||||||
import ProgressCustom from "./Progress/ProgressCustom";
|
import ProgressCustom from "./Progress/ProgressCustom";
|
||||||
|
// Loader
|
||||||
|
import LoaderCustom from "./Loader/LoaderCustom";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// ActionIcon
|
// ActionIcon
|
||||||
@@ -128,4 +130,6 @@ export {
|
|||||||
TextInputCustom,
|
TextInputCustom,
|
||||||
// ViewWrapper
|
// ViewWrapper
|
||||||
ViewWrapper,
|
ViewWrapper,
|
||||||
|
// Loader
|
||||||
|
LoaderCustom,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
await AsyncStorage.setItem("authToken", token);
|
await AsyncStorage.setItem("authToken", token);
|
||||||
|
|
||||||
const responseUser = await apiConfig.get(
|
const responseUser = await apiConfig.get(
|
||||||
`/mobile/user?token=${token}`,
|
`/mobile?token=${token}`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { apiConfig } from "../api-config";
|
import { apiConfig } from "../api-config";
|
||||||
|
|
||||||
export async function apiUser(id: string) {
|
export async function apiUser(id: string) {
|
||||||
const response = await apiConfig.get(`/user/${id}`);
|
const response = await apiConfig.get(`/mobile/user/${id}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function apiAllUser({ search }: { search: string }) {
|
||||||
|
const response = await apiConfig.get(`/mobile/user?search=${search}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user