feat: polish
This commit is contained in:
@@ -1,76 +1,73 @@
|
|||||||
import { getVersion } from '@tauri-apps/api/app';
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import { relaunch } from '@tauri-apps/plugin-process';
|
import { relaunch } from "@tauri-apps/plugin-process";
|
||||||
import { Update, check } from '@tauri-apps/plugin-updater';
|
import { Update, check } from "@tauri-apps/plugin-updater";
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from "react";
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from "react-router-dom";
|
||||||
import { toast } from 'sonner';
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function AboutScreen() {
|
export function AboutScreen() {
|
||||||
const [version, setVersion] = useState('');
|
const [version, setVersion] = useState("");
|
||||||
const [newUpdate, setNewUpdate] = useState<Update>(null);
|
const [newUpdate, setNewUpdate] = useState<Update>(null);
|
||||||
|
|
||||||
const checkUpdate = async () => {
|
const checkUpdate = async () => {
|
||||||
const update = await check();
|
const update = await check();
|
||||||
if (!update) toast.info('There is no update available');
|
if (!update) toast.info("There is no update available");
|
||||||
setNewUpdate(update);
|
setNewUpdate(update);
|
||||||
};
|
};
|
||||||
|
|
||||||
const installUpdate = async () => {
|
const installUpdate = async () => {
|
||||||
await newUpdate.downloadAndInstall();
|
await newUpdate.downloadAndInstall();
|
||||||
await relaunch();
|
await relaunch();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadVersion() {
|
async function loadVersion() {
|
||||||
const appVersion = await getVersion();
|
const appVersion = await getVersion();
|
||||||
setVersion(appVersion);
|
setVersion(appVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadVersion();
|
loadVersion();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-lg">
|
<div className="mx-auto w-full max-w-lg">
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex flex-col items-center">
|
||||||
<img src="/icon.png" alt="Lume's logo" className="w-16 shrink-0" />
|
<h1 className="leading-tight text-xl font-semibold">Lume</h1>
|
||||||
<div>
|
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
||||||
<h1 className="text-xl font-semibold">Lume</h1>
|
Version {version}
|
||||||
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
|
</p>
|
||||||
Version {version}
|
</div>
|
||||||
</p>
|
<div className="mx-auto mt-4 flex w-full max-w-xs flex-col gap-2">
|
||||||
</div>
|
{!newUpdate ? (
|
||||||
</div>
|
<button
|
||||||
<div className="mx-auto mt-4 flex w-full max-w-xs flex-col gap-2">
|
type="button"
|
||||||
{!newUpdate ? (
|
onClick={() => checkUpdate()}
|
||||||
<button
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
|
||||||
type="button"
|
>
|
||||||
onClick={() => checkUpdate()}
|
Check for update
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
|
</button>
|
||||||
>
|
) : (
|
||||||
Check for update
|
<button
|
||||||
</button>
|
type="button"
|
||||||
) : (
|
onClick={() => installUpdate()}
|
||||||
<button
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
|
||||||
type="button"
|
>
|
||||||
onClick={() => installUpdate()}
|
Install {newUpdate.version}
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
|
</button>
|
||||||
>
|
)}
|
||||||
Install {newUpdate.version}
|
<Link
|
||||||
</button>
|
to="https://lume.nu"
|
||||||
)}
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
<Link
|
>
|
||||||
to="https://lume.nu"
|
Website
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
</Link>
|
||||||
>
|
<Link
|
||||||
Website
|
to="https://github.com/luminous-devs/lume/issues"
|
||||||
</Link>
|
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
<Link
|
>
|
||||||
to="https://github.com/luminous-devs/lume/issues"
|
Report a issue
|
||||||
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
</Link>
|
||||||
>
|
</div>
|
||||||
Report a issue
|
</div>
|
||||||
</Link>
|
);
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ export function NWCScreen() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex w-full flex-col gap-5">
|
<div className="flex w-full flex-col gap-5">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="text-2xl font-semibold leading-tight">
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
Nostr Wallet Connect
|
Nostr Wallet Connect
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">
|
<p className="font-medium leading-snug text-neutral-600 dark:text-neutral-500">
|
||||||
Sending zap easily via Bitcoin Lightning.
|
Sending zap easily via Bitcoin Lightning.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export class Ark {
|
|||||||
.replace("nostr:", "")
|
.replace("nostr:", "")
|
||||||
.split("'")[0]
|
.split("'")[0]
|
||||||
.split(".")[0]
|
.split(".")[0]
|
||||||
|
.split(",")[0]
|
||||||
.split("?")[0];
|
.split("?")[0];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export function RepostNote({
|
|||||||
<Note.Content />
|
<Note.Content />
|
||||||
<div className="flex items-center justify-between h-14">
|
<div className="flex items-center justify-between h-14">
|
||||||
<Note.Pin />
|
<Note.Pin />
|
||||||
<div className="inline-flex items-center gap-10">
|
<div className="inline-flex items-center gap-4">
|
||||||
<Note.Reply />
|
<Note.Reply />
|
||||||
<Note.Repost />
|
<Note.Repost />
|
||||||
<Note.Zap />
|
<Note.Zap />
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function TextNote({
|
|||||||
<Note.Content className="min-w-0 px-3" />
|
<Note.Content className="min-w-0 px-3" />
|
||||||
<div className="flex items-center justify-between px-3 h-14">
|
<div className="flex items-center justify-between px-3 h-14">
|
||||||
<Note.Pin />
|
<Note.Pin />
|
||||||
<div className="inline-flex items-center gap-10">
|
<div className="inline-flex items-center gap-4">
|
||||||
<Note.Reply />
|
<Note.Reply />
|
||||||
<Note.Repost />
|
<Note.Repost />
|
||||||
<Note.Zap />
|
<Note.Zap />
|
||||||
|
|||||||
28
packages/ark/src/components/user/cover.tsx
Normal file
28
packages/ark/src/components/user/cover.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { cn } from "@lume/utils";
|
||||||
|
import { useUserContext } from "./provider";
|
||||||
|
|
||||||
|
export function UserCover({ className }: { className?: string }) {
|
||||||
|
const user = useUserContext();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"animate-pulse bg-neutral-300 dark:bg-neutral-700",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={user.banner || user.cover}
|
||||||
|
alt="banner"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
style={{ contentVisibility: "auto" }}
|
||||||
|
className={cn("object-cover", className)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { UserAbout } from "./about";
|
import { UserAbout } from "./about";
|
||||||
import { UserAvatar } from "./avatar";
|
import { UserAvatar } from "./avatar";
|
||||||
|
import { UserCover } from "./cover";
|
||||||
import { UserFollowButton } from "./followButton";
|
import { UserFollowButton } from "./followButton";
|
||||||
import { UserName } from "./name";
|
import { UserName } from "./name";
|
||||||
import { UserNip05 } from "./nip05";
|
import { UserNip05 } from "./nip05";
|
||||||
@@ -11,6 +12,7 @@ export const User = {
|
|||||||
Provider: UserProvider,
|
Provider: UserProvider,
|
||||||
Root: UserRoot,
|
Root: UserRoot,
|
||||||
Avatar: UserAvatar,
|
Avatar: UserAvatar,
|
||||||
|
Cover: UserCover,
|
||||||
Name: UserName,
|
Name: UserName,
|
||||||
NIP05: UserNip05,
|
NIP05: UserNip05,
|
||||||
Time: UserTime,
|
Time: UserTime,
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ export class NDKCacheAdapterTauri implements NDKCacheAdapter {
|
|||||||
|
|
||||||
const events = await this.#storage.getCacheEvents(eventIds);
|
const events = await this.#storage.getCacheEvents(eventIds);
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
let rawEvent;
|
let rawEvent: NostrEvent;
|
||||||
try {
|
try {
|
||||||
rawEvent = JSON.parse(event.event);
|
rawEvent = JSON.parse(event.event);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function ActiveAccount() {
|
|||||||
return (
|
return (
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<DropdownMenu.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<div>
|
<div className="relative">
|
||||||
<Avatar.Root>
|
<Avatar.Root>
|
||||||
<Avatar.Image
|
<Avatar.Image
|
||||||
src={user?.picture || user?.image}
|
src={user?.picture || user?.image}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { HorizontalDotsIcon } from "@lume/icons";
|
|
||||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { Logout } from "./logout";
|
|
||||||
|
|
||||||
export function AccountMoreActions() {
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Root>
|
|
||||||
<DropdownMenu.Trigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="inline-flex items-center justify-center rounded-md"
|
|
||||||
>
|
|
||||||
<HorizontalDotsIcon className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownMenu.Content className="ml-2 flex w-[200px] flex-col overflow-hidden rounded-xl bg-blue-500 p-2 focus:outline-none">
|
|
||||||
<DropdownMenu.Item asChild>
|
|
||||||
<Link
|
|
||||||
to="/settings/"
|
|
||||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</Link>
|
|
||||||
</DropdownMenu.Item>
|
|
||||||
<Logout />
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenu.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
export * from "./account/active";
|
export * from "./account/active";
|
||||||
export * from "./account/logout";
|
export * from "./account/logout";
|
||||||
export * from "./account/more";
|
|
||||||
export * from "./navigation";
|
export * from "./navigation";
|
||||||
export * from "./nip05";
|
|
||||||
export * from "./user";
|
|
||||||
export * from "./titlebar";
|
export * from "./titlebar";
|
||||||
export * from "./layouts/app";
|
export * from "./layouts/app";
|
||||||
export * from "./layouts/auth";
|
export * from "./layouts/auth";
|
||||||
@@ -15,3 +12,4 @@ export * from "./emptyFeed";
|
|||||||
export * from "./routes/event";
|
export * from "./routes/event";
|
||||||
export * from "./routes/user";
|
export * from "./routes/user";
|
||||||
export * from "./translateRegisterModal";
|
export * from "./translateRegisterModal";
|
||||||
|
export * from "./user";
|
||||||
|
|||||||
@@ -15,12 +15,7 @@ export function OnboardingFinishScreen() {
|
|||||||
const finish = async () => {
|
const finish = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const queryCache = queryClient.getQueryCache();
|
await queryClient.refetchQueries({ queryKey: ["timeline-9999"] });
|
||||||
const queryKeys = queryCache.getAll().map((cache) => cache.queryKey);
|
|
||||||
|
|
||||||
for (const key of queryKeys) {
|
|
||||||
await queryClient.refetchQueries({ queryKey: key });
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setOnboarding(false);
|
setOnboarding(false);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useArk } from "@lume/ark";
|
import { User, useArk } from "@lume/ark";
|
||||||
import {
|
import {
|
||||||
ArrowLeftIcon,
|
ArrowLeftIcon,
|
||||||
CancelIcon,
|
CancelIcon,
|
||||||
@@ -14,7 +14,6 @@ import { nip19 } from "nostr-tools";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { User } from "../user";
|
|
||||||
|
|
||||||
const POPULAR_USERS = [
|
const POPULAR_USERS = [
|
||||||
"npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6",
|
"npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6",
|
||||||
@@ -66,6 +65,7 @@ export function OnboardingFollowScreen() {
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (!follows.length) return navigate("/finish");
|
if (!follows.length) return navigate("/finish");
|
||||||
|
|
||||||
const publish = await ark.newContactList({
|
const publish = await ark.newContactList({
|
||||||
@@ -118,7 +118,18 @@ export function OnboardingFollowScreen() {
|
|||||||
key={pubkey}
|
key={pubkey}
|
||||||
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
||||||
>
|
>
|
||||||
<User pubkey={pubkey} variant="large" />
|
<User.Provider pubkey={pubkey}>
|
||||||
|
<User.Root>
|
||||||
|
<User.Cover className="h-20 w-full rounded-t-lg" />
|
||||||
|
<div className="flex h-full w-full flex-col gap-2.5 px-3 -mt-6">
|
||||||
|
<User.Avatar className="size-10 shrink-0 rounded-lg" />
|
||||||
|
<div className="flex flex-col items-start text-start">
|
||||||
|
<User.Name className="max-w-[15rem] truncate text-lg font-semibold leadning-tight" />
|
||||||
|
<User.About className="break-p text-neutral-700 dark:text-neutral-600 max-w-none select-text whitespace-pre-line" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -172,7 +183,18 @@ export function OnboardingFollowScreen() {
|
|||||||
key={item.pubkey}
|
key={item.pubkey}
|
||||||
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
||||||
>
|
>
|
||||||
<User pubkey={item.pubkey} variant="large" />
|
<User.Provider pubkey={item.pubkey}>
|
||||||
|
<User.Root>
|
||||||
|
<User.Cover className="h-20 w-full rounded-t-lg" />
|
||||||
|
<div className="flex h-full w-full flex-col gap-2.5 px-3 -mt-6">
|
||||||
|
<User.Avatar className="size-10 shrink-0 rounded-lg" />
|
||||||
|
<div className="flex flex-col items-start text-start">
|
||||||
|
<User.Name className="max-w-[15rem] truncate text-lg font-semibold leadning-tight" />
|
||||||
|
<User.About className="break-p text-neutral-700 dark:text-neutral-600 max-w-none select-text whitespace-pre-line" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -196,6 +218,7 @@ export function OnboardingFollowScreen() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
UU
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
@@ -218,7 +241,18 @@ export function OnboardingFollowScreen() {
|
|||||||
key={pubkey}
|
key={pubkey}
|
||||||
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
className="flex h-max w-full shrink-0 flex-col my-3 gap-4 overflow-hidden rounded-lg bg-white dark:bg-black"
|
||||||
>
|
>
|
||||||
<User pubkey={pubkey} variant="large" />
|
<User.Provider pubkey={pubkey}>
|
||||||
|
<User.Root>
|
||||||
|
<User.Cover className="h-20 w-full rounded-t-lg" />
|
||||||
|
<div className="flex h-full w-full flex-col gap-2.5 px-3 -mt-6">
|
||||||
|
<User.Avatar className="size-10 shrink-0 rounded-lg" />
|
||||||
|
<div className="flex flex-col items-start text-start">
|
||||||
|
<User.Name className="max-w-[15rem] truncate text-lg font-semibold leadning-tight" />
|
||||||
|
<User.About className="break-p text-neutral-700 dark:text-neutral-600 max-w-none select-text whitespace-pre-line" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</User.Root>
|
||||||
|
</User.Provider>
|
||||||
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
<div className="h-16 shrink-0 px-3 flex items-center border-t border-neutral-100 dark:border-neutral-900">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export function OnboardingProfileSettingsScreen() {
|
|||||||
nip05: oldProfile?.nip05 || "",
|
nip05: oldProfile?.nip05 || "",
|
||||||
display_name: data.name,
|
display_name: data.name,
|
||||||
bio: data.about,
|
bio: data.about,
|
||||||
|
image: picture,
|
||||||
picture: picture,
|
picture: picture,
|
||||||
avatar: picture,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const publish = await ark.createEvent({
|
const publish = await ark.createEvent({
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ export function ReplyList({
|
|||||||
className="py-4 border-t border-neutral-100 dark:border-neutral-900"
|
className="py-4 border-t border-neutral-100 dark:border-neutral-900"
|
||||||
/>
|
/>
|
||||||
{!data ? (
|
{!data ? (
|
||||||
<div className="pt-4 flex h-16 items-center justify-center rounded-xl bg-neutral-50 p-3 dark:bg-neutral-950">
|
<div className="mt-4 flex h-16 items-center justify-center p-3">
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin" />
|
<LoaderIcon className="h-5 w-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : data.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="pt-4 flex w-full items-center justify-center bg-neutral-50 dark:bg-neutral-950 rounded-lg">
|
<div className="mt-4 flex w-full items-center justify-center">
|
||||||
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
<div className="flex flex-col items-center justify-center gap-2 py-6">
|
||||||
<h3 className="text-3xl">👋</h3>
|
<h3 className="text-3xl">👋</h3>
|
||||||
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
<p className="leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
|||||||
Reference in New Issue
Block a user