Moving to a todo app
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import Store from '../store';
|
||||
|
||||
const MenuItem = ({ children, ...props }) => (
|
||||
<li {...props}>
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-800 hover:text-gray-400 block px-4 py-2 rounded-md text-base font-medium"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
||||
const MenuContent = () => {
|
||||
const pages = Store.useState(s => s.pages);
|
||||
|
||||
const go = page => {
|
||||
Store.update(s => {
|
||||
console.log('Going to', page);
|
||||
s.currentPage = page;
|
||||
s.showMenu = false;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl select-none">Menu</h2>
|
||||
</div>
|
||||
<ul>
|
||||
{pages.map(p => (
|
||||
<MenuItem key={p.id} onClick={() => go(p)}>
|
||||
{p.title}
|
||||
</MenuItem>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MenuContent;
|
||||
@@ -0,0 +1,45 @@
|
||||
import { checkmarkOutline, closeOutline } from 'ionicons/icons';
|
||||
import Button from './ui/Button';
|
||||
import Icon from './ui/Icon';
|
||||
import List from './ui/List';
|
||||
import ListItem from './ui/ListItem';
|
||||
import VirtualScroll from './ui/VirtualScroll';
|
||||
|
||||
const NotificationItem = ({ i }) => (
|
||||
<ListItem className="flex align-center">
|
||||
<img
|
||||
src={`/img/faces/image-${(i % 66) + 1}.png`}
|
||||
alt="Notification"
|
||||
className="block rounded-full w-8 h-8 mr-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<span className="p-0 m-0 align-middle">You have a new friend request</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button className="background-transparent px-1 py-1 text-green-400 text-lg">
|
||||
<Icon icon={checkmarkOutline} />
|
||||
</Button>
|
||||
<Button className="background-transparent px-1 py-1 text-red-400 text-lg">
|
||||
<Icon icon={closeOutline} />
|
||||
</Button>
|
||||
</div>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
const Notifications = () => (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl">Notifications</h2>
|
||||
</div>
|
||||
<List className="flex-1">
|
||||
<VirtualScroll
|
||||
totalCount={1000}
|
||||
overscan={200}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
itemContent={index => <NotificationItem i={index} />}
|
||||
/>
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Notifications;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
import Content from '../ui/Content';
|
||||
import List from '../ui/List';
|
||||
import ListItem from '../ui/ListItem';
|
||||
|
||||
const Feed = ({ selected }) => {
|
||||
return (
|
||||
<Content visible={selected} className="p-4">
|
||||
<List className="h-full w-full">
|
||||
{selected && (
|
||||
<Virtuoso
|
||||
totalCount={1000}
|
||||
overscan={200}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
itemContent={index => <ListItem>Item {index}</ListItem>}
|
||||
/>
|
||||
)}
|
||||
</List>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default Feed;
|
||||
@@ -1,14 +1,12 @@
|
||||
import { homeItems } from '../../data';
|
||||
import Store from '../../store';
|
||||
import Card from '../ui/Card';
|
||||
import Content from '../ui/Content';
|
||||
|
||||
const PostCard = ({ title, type, text, author, image }) => (
|
||||
<Card>
|
||||
const HomeCard = ({ title, type, text, author, image }) => (
|
||||
<Card className="my-4">
|
||||
<div>
|
||||
<img
|
||||
className="rounded-t-xl h-32 w-full object-cover"
|
||||
src={image || 'https://ionic-docs-demo.herokuapp.com/assets/card-top-img.png'}
|
||||
/>
|
||||
<img className="rounded-t-xl h-32 w-full object-cover" src={image} />
|
||||
</div>
|
||||
<div className="px-4 py-4 mt-2 bg-white rounded-b-xl">
|
||||
<h4 className="font-bold py-0 text-s text-gray-400 uppercase">{type}</h4>
|
||||
@@ -19,10 +17,12 @@ const PostCard = ({ title, type, text, author, image }) => (
|
||||
);
|
||||
|
||||
const Home = ({ selected }) => {
|
||||
const homeItems = Store.useState(s => s.homeItems);
|
||||
|
||||
return (
|
||||
<Content visible={selected}>
|
||||
<Content visible={selected} className="p-4">
|
||||
{homeItems.map((i, index) => (
|
||||
<PostCard {...i} key={index} />
|
||||
<HomeCard {...i} key={index} />
|
||||
))}
|
||||
</Content>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useState } from 'react';
|
||||
import Store from '../../store';
|
||||
import { setDone } from '../../store/actions';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
import Content from '../ui/Content';
|
||||
import List from '../ui/List';
|
||||
import ListItem from '../ui/ListItem';
|
||||
import VirtualScroll from '../ui/VirtualScroll';
|
||||
|
||||
const ListEntry = ({ list, ...props }) => (
|
||||
<div {...props} className="p-4 border-solid border-b cursor-pointer">
|
||||
<span className="text-md">{list.name}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AllLists = ({ onSelect }) => {
|
||||
const lists = Store.useState(s => s.lists);
|
||||
|
||||
return (
|
||||
<VirtualScroll
|
||||
data={lists}
|
||||
totalCount={lists.length}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
itemContent={(i, list) => (
|
||||
<ListEntry list={list} onClick={() => onSelect(list)} onClose={() => onSelect(null)} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItems = ({ list, onClose }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="py-2">
|
||||
<a href="#" onClick={onClose}>
|
||||
All Lists
|
||||
</a>
|
||||
</div>
|
||||
<VirtualScroll
|
||||
data={list.items || []}
|
||||
totalCount={(list.items || []).length}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
itemContent={(i, item) => <ListItemEntry list={list} item={item} />}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItemEntry = ({ list, item }) => (
|
||||
<div className="p-4 border-solid border-b cursor-pointer">
|
||||
<span className="text-md">{item.name}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={item.done}
|
||||
onChange={() => {
|
||||
setDone(list, item, !item.done);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const Feed = ({ selected }) => {
|
||||
const selectedList = Store.useState(s => s.selectedList);
|
||||
|
||||
return (
|
||||
<Content visible={selected} className="p-4">
|
||||
<List className="h-full w-full">
|
||||
{selected && selectedList ? (
|
||||
<ListItems
|
||||
list={selectedList}
|
||||
onClose={() =>
|
||||
Store.update(s => {
|
||||
s.selectedList = null;
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<AllLists
|
||||
onSelect={list => {
|
||||
Store.update(s => {
|
||||
s.selectedList = list;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</List>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default Feed;
|
||||
@@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
const Card = ({ children, className, ...props }) => (
|
||||
<div {...props} className={classNames('m-auto px-4 py-4 max-w-xl', className)}>
|
||||
<div {...props} className={classNames('max-w-xl', className)}>
|
||||
<div className="bg-white shadow-md rounded-b-xl">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
const PageStack = ({ children, className, ...props }) => (
|
||||
<div {...props} className={classNames('flex-1 z-0 overflow-hidden relative', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PageStack;
|
||||
@@ -1,12 +1,13 @@
|
||||
const TabBar = ({ children }) => (
|
||||
<nav
|
||||
id="tab-bar"
|
||||
className="py-2 h-16 bg-white w-full flex justify-center items-start bg-gray-50"
|
||||
className="py-2 h-16 bg-white w-full flex justify-center items-start bg-gray-50 z-10"
|
||||
style={{
|
||||
height: `calc(env(safe-area-inset-bottom, 0px) + 56px)`
|
||||
}}>
|
||||
height: `calc(env(safe-area-inset-bottom, 0px) + 56px)`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</nav>
|
||||
)
|
||||
);
|
||||
|
||||
export default TabBar;
|
||||
export default TabBar;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
const Toggle = () => <div></div>;
|
||||
|
||||
export default Toggle;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
|
||||
const VirtualScroll = props => <Virtuoso {...props} />;
|
||||
|
||||
export default VirtualScroll;
|
||||
@@ -1,47 +0,0 @@
|
||||
// Some fake filler data
|
||||
|
||||
export const images = [
|
||||
'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||
'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||
];
|
||||
|
||||
export const homeItems = [
|
||||
{
|
||||
title: 'Welcome',
|
||||
type: 'Blog',
|
||||
text: 'Welcome to the app!',
|
||||
author: 'Max',
|
||||
image: images[0],
|
||||
},
|
||||
{
|
||||
title: 'How to get started',
|
||||
type: 'Article',
|
||||
text: 'Getting started with the app is easy! Just follow these 100 steps',
|
||||
author: 'Max',
|
||||
image: images[1],
|
||||
},
|
||||
{
|
||||
title: 'Need help?',
|
||||
type: 'Support',
|
||||
text: "We're here to help. Available between the hours of 3am and 3:01am every day",
|
||||
author: 'Max',
|
||||
image: images[2],
|
||||
},
|
||||
{
|
||||
title: 'Welcome',
|
||||
type: 'Blog',
|
||||
text: 'Welcome to the app!',
|
||||
author: 'Max',
|
||||
image: images[3],
|
||||
},
|
||||
{
|
||||
title: 'Welcome',
|
||||
type: 'Blog',
|
||||
text: 'Welcome to the app!',
|
||||
author: 'Max',
|
||||
image: images[4],
|
||||
},
|
||||
];
|
||||
+22
-95
@@ -1,7 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { flash, flashOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
|
||||
|
||||
import Store from '../store';
|
||||
|
||||
import App from '../components/ui/App';
|
||||
@@ -9,112 +5,34 @@ import Backdrop from '../components/ui/Backdrop';
|
||||
import Menu from '../components/ui/Menu';
|
||||
import Modal from '../components/ui/Modal';
|
||||
import Nav from '../components/ui/Nav';
|
||||
import PageStack from '../components/ui/PageStack';
|
||||
import Tab from '../components/ui/Tab';
|
||||
import TabBar from '../components/ui/TabBar';
|
||||
import List from '../components/ui/List';
|
||||
import ListItem from '../components/ui/ListItem';
|
||||
import Button from '../components/ui/Button';
|
||||
import { SafeAreaProvider } from '../components/ui/SafeArea';
|
||||
|
||||
import Home from '../components/pages/Home';
|
||||
import Feed from '../components/pages/Feed';
|
||||
import Settings from '../components/pages/Settings';
|
||||
import { useDrag } from 'react-use-gesture';
|
||||
|
||||
const pages = [
|
||||
{ id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
|
||||
{
|
||||
id: 'feed',
|
||||
title: 'Feed',
|
||||
icon: flashOutline,
|
||||
selectedIcon: flash,
|
||||
component: Feed,
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'Settings',
|
||||
icon: cogOutline,
|
||||
selectedIcon: cog,
|
||||
component: Settings,
|
||||
},
|
||||
];
|
||||
import Notifications from '../components/Notifications';
|
||||
import MenuContent from '../components/MenuContent';
|
||||
|
||||
const CurrentPage = ({ page }) => {
|
||||
const pages = Store.useState(s => s.pages);
|
||||
|
||||
return (
|
||||
<div className="flex-1 z-0 overflow-hidden relative">
|
||||
<PageStack>
|
||||
{pages.map(p => {
|
||||
const Page = p.component;
|
||||
return <Page selected={page.id === p.id} key={p.id} />;
|
||||
})}
|
||||
</div>
|
||||
</PageStack>
|
||||
);
|
||||
};
|
||||
|
||||
const MenuItem = ({ children }) => (
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
className="text-gray-800 hover:text-gray-400 block px-4 py-2 rounded-md text-base font-medium"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
const MenuContent = () => (
|
||||
<>
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl select-none">Menu</h2>
|
||||
</div>
|
||||
<ul>
|
||||
<MenuItem>Home</MenuItem>
|
||||
<MenuItem>Profile</MenuItem>
|
||||
<MenuItem>Settings</MenuItem>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
|
||||
const FakeNotification = ({ i }) => (
|
||||
<ListItem className="flex align-center">
|
||||
<img
|
||||
src={`/img/faces/image-${(i % 66) + 1}.png`}
|
||||
alt="Notification"
|
||||
className="block rounded-full w-8 h-8 mr-4"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<span className="p-0 m-0 align-middle">You have a new friend request</span>
|
||||
</div>
|
||||
<div>
|
||||
<Button className="background-transparent px-1 py-1 text-green-400 text-lg">
|
||||
<ion-icon name="checkmark-outline" />
|
||||
</Button>
|
||||
<Button className="background-transparent px-1 py-1 text-red-400 text-lg">
|
||||
<ion-icon name="close-outline" />
|
||||
</Button>
|
||||
</div>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
const NotificationsContent = () => (
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<div className="p-4">
|
||||
<h2 className="text-xl">Notifications</h2>
|
||||
</div>
|
||||
<List className="flex-1">
|
||||
<Virtuoso
|
||||
totalCount={1000}
|
||||
overscan={200}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
itemContent={index => <FakeNotification i={index} />}
|
||||
/>
|
||||
</List>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function Index() {
|
||||
const [page, setPage] = useState(pages[0]);
|
||||
|
||||
const showMenu = Store.useState(s => s.showMenu);
|
||||
const showNotifications = Store.useState(s => s.showNotifications);
|
||||
const currentPage = Store.useState(s => s.currentPage);
|
||||
const pages = Store.useState(s => s.pages);
|
||||
|
||||
const closeMenu = () => {
|
||||
Store.update(s => {
|
||||
@@ -163,16 +81,25 @@ export default function Index() {
|
||||
<Menu open={showMenu} onClose={closeMenu}>
|
||||
<MenuContent />
|
||||
</Menu>
|
||||
<Nav page={page} />
|
||||
<CurrentPage page={page} />
|
||||
<Nav page={currentPage} />
|
||||
<CurrentPage page={currentPage} />
|
||||
<TabBar>
|
||||
{pages.map(p => (
|
||||
<Tab key={p.id} {...p} onClick={() => setPage(p)} selected={p.id === page.id} />
|
||||
<Tab
|
||||
key={p.id}
|
||||
{...p}
|
||||
onClick={() =>
|
||||
Store.update(s => {
|
||||
s.currentPage = p;
|
||||
})
|
||||
}
|
||||
selected={p.id === currentPage.id}
|
||||
/>
|
||||
))}
|
||||
</TabBar>
|
||||
<Backdrop open={showMenu || showNotifications} onClose={backdropClose} />
|
||||
<Modal open={showNotifications} onClose={closeNotifications}>
|
||||
<NotificationsContent />
|
||||
<Notifications />
|
||||
</Modal>
|
||||
</SafeAreaProvider>
|
||||
</App>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import Store from '.';
|
||||
|
||||
export const setDone = (list, item, done) => {
|
||||
Store.update((s, o) => {
|
||||
const listIndex = o.lists.findIndex(l => l === list);
|
||||
const itemIndex = o.lists[listIndex].items.findIndex(i => i === item);
|
||||
s.lists[listIndex].items[itemIndex].done = done;
|
||||
if (list === o.selectedList) {
|
||||
s.selectedList = s.lists[listIndex];
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1,10 +1,96 @@
|
||||
import { Store as PullStateStore } from 'pullstate';
|
||||
|
||||
import { list, listOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
|
||||
|
||||
import Home from '../components/pages/Home';
|
||||
import Lists from '../components/pages/Lists';
|
||||
import Settings from '../components/pages/Settings';
|
||||
|
||||
// The available pages here
|
||||
const pages = [
|
||||
{ id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
|
||||
{
|
||||
id: 'lists',
|
||||
title: 'Lists',
|
||||
icon: listOutline,
|
||||
selectedIcon: list,
|
||||
component: Lists,
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
title: 'Settings',
|
||||
icon: cogOutline,
|
||||
selectedIcon: cog,
|
||||
component: Settings,
|
||||
},
|
||||
];
|
||||
|
||||
export const images = [
|
||||
'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||
'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
|
||||
'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
|
||||
];
|
||||
|
||||
export const homeItems = [
|
||||
{
|
||||
title: 'Welcome',
|
||||
type: 'Blog',
|
||||
text: 'Welcome to the app!',
|
||||
author: 'Max',
|
||||
image: images[0],
|
||||
},
|
||||
{
|
||||
title: 'How to get started',
|
||||
type: 'Article',
|
||||
text: 'Getting started with the app is easy! Just follow these 100 steps',
|
||||
author: 'Max',
|
||||
image: images[1],
|
||||
},
|
||||
{
|
||||
title: 'Need help?',
|
||||
type: 'Support',
|
||||
text: "We're here to help. Available between the hours of 3am and 3:01am every day",
|
||||
author: 'Max',
|
||||
image: images[2],
|
||||
},
|
||||
];
|
||||
|
||||
// Some fake lists
|
||||
const lists = [
|
||||
{
|
||||
name: 'Groceries',
|
||||
id: 'groceries',
|
||||
items: [{ name: 'Apples' }, { name: 'Bananas' }, { name: 'Milk' }, { name: 'Ice Cream' }],
|
||||
},
|
||||
{
|
||||
name: 'Hardware Store',
|
||||
id: 'hardware',
|
||||
items: [
|
||||
{ name: 'Circular Saw' },
|
||||
{ name: 'Tack Cloth' },
|
||||
{ name: 'Drywall' },
|
||||
{ name: 'Router' },
|
||||
],
|
||||
},
|
||||
{ name: 'Work', id: 'work', items: [{ name: 'TPS Report' }, { name: 'Set up email' }] },
|
||||
{ name: 'Reminders', id: 'reminders' },
|
||||
];
|
||||
|
||||
const Store = new PullStateStore({
|
||||
safeAreaTop: 0,
|
||||
safeAreaBottom: 0,
|
||||
showMenu: false,
|
||||
showNotifications: false,
|
||||
currentPage: pages[0],
|
||||
pages,
|
||||
homeItems,
|
||||
lists,
|
||||
selectedList: null,
|
||||
settings: {
|
||||
enableNotifications: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default Store;
|
||||
|
||||
Reference in New Issue
Block a user