New routing setup #2395
21 changed files with 99 additions and 103 deletions
|
@ -45,7 +45,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
|
||||||
button="link"
|
button="link"
|
||||||
className="load-screen__button"
|
className="load-screen__button"
|
||||||
label={__('refreshing the app')}
|
label={__('refreshing the app')}
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => (window.location.href = '/')}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{__('to fix it')}.
|
{__('to fix it')}.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -53,7 +53,7 @@ const Header = (props: Props) => {
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--back"
|
className="header__navigation-item header__navigation-item--back"
|
||||||
description={__('Navigate back')}
|
description={__('Navigate back')}
|
||||||
disabled={isBackDisabled}
|
onClick={() => window.history.back()}
|
||||||
icon={ICONS.ARROW_LEFT}
|
icon={ICONS.ARROW_LEFT}
|
||||||
iconSize={15}
|
iconSize={15}
|
||||||
/>
|
/>
|
||||||
|
@ -61,7 +61,7 @@ const Header = (props: Props) => {
|
||||||
<Button
|
<Button
|
||||||
className="header__navigation-item header__navigation-item--forward"
|
className="header__navigation-item header__navigation-item--forward"
|
||||||
description={__('Navigate forward')}
|
description={__('Navigate forward')}
|
||||||
disabled={isForwardDisabled}
|
onClick={() => window.history.forward()}
|
||||||
icon={ICONS.ARROW_RIGHT}
|
icon={ICONS.ARROW_RIGHT}
|
||||||
iconSize={15}
|
iconSize={15}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -105,12 +105,10 @@ class TransactionList extends React.PureComponent<Props> {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
{!transactionList.length && (
|
{!transactionList.length && <p>{emptyMessage || __('No transactions to list.')}</p>}
|
||||||
<p className="card__content">{emptyMessage || __('No transactions to list.')}</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!!transactionList.length && (
|
{!!transactionList.length && (
|
||||||
<div className="card__content">
|
<React.Fragment>
|
||||||
<table className="table table--transactions">
|
<table className="table table--transactions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -133,7 +131,7 @@ class TransactionList extends React.PureComponent<Props> {
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,14 +53,14 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
||||||
transactions={transactions}
|
transactions={transactions}
|
||||||
emptyMessage={__("Looks like you don't have any recent transactions.")}
|
emptyMessage={__("Looks like you don't have any recent transactions.")}
|
||||||
/>
|
/>
|
||||||
<div className="card__actions">
|
</div>
|
||||||
<Button
|
<div className="card__actions">
|
||||||
button="primary"
|
<Button
|
||||||
navigate="/$/history"
|
button="primary"
|
||||||
label={__('Full History')}
|
navigate="/$/history"
|
||||||
icon={icons.HISTORY}
|
label={__('Full History')}
|
||||||
/>
|
icon={icons.HISTORY}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,10 +4,17 @@ import { selectCurrentParams, makeSelectCurrentParam } from 'lbry-redux';
|
||||||
import { doClearContentHistoryUri } from 'redux/actions/content';
|
import { doClearContentHistoryUri } from 'redux/actions/content';
|
||||||
import UserHistory from './view';
|
import UserHistory from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = (state, props) => {
|
||||||
pageCount: selectHistoryPageCount(state),
|
const { search } = props.location;
|
||||||
// history: makeSelectHistoryForPage(paramPage)(state),
|
const urlParams = new URLSearchParams(search);
|
||||||
});
|
const page = Number(urlParams.get('page')) || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
page,
|
||||||
|
pageCount: selectHistoryPageCount(state),
|
||||||
|
history: makeSelectHistoryForPage(page)(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)),
|
clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Button from 'component/button';
|
||||||
import { Form, FormField } from 'component/common/form';
|
import { Form, FormField } from 'component/common/form';
|
||||||
import ReactPaginate from 'react-paginate';
|
import ReactPaginate from 'react-paginate';
|
||||||
import UserHistoryItem from 'component/userHistoryItem';
|
import UserHistoryItem from 'component/userHistoryItem';
|
||||||
|
import { navigate } from '@reach/router';
|
||||||
|
|
||||||
type HistoryItem = {
|
type HistoryItem = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -51,9 +52,8 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
changePage(pageNumber: number) {
|
changePage(pageNumber: number) {
|
||||||
const { params } = this.props;
|
console.log('new', pageNumber);
|
||||||
const newParams = { ...params, page: pageNumber };
|
navigate(`/$/user_history?page=${pageNumber}`);
|
||||||
// this.props.navigate('/user_history', newParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
paginate(e: SyntheticKeyboardEvent<*>) {
|
paginate(e: SyntheticKeyboardEvent<*>) {
|
||||||
|
@ -94,6 +94,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { history = [], page, pageCount } = this.props;
|
const { history = [], page, pageCount } = this.props;
|
||||||
|
console.log('this.props', this.props);
|
||||||
const { itemsSelected } = this.state;
|
const { itemsSelected } = this.state;
|
||||||
const allSelected = Object.keys(itemsSelected).length === history.length;
|
const allSelected = Object.keys(itemsSelected).length === history.length;
|
||||||
const selectHandler = allSelected ? this.unselectAll : this.selectAll;
|
const selectHandler = allSelected ? this.unselectAll : this.selectAll;
|
||||||
|
@ -147,6 +148,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
|
||||||
onPageChange={e => this.changePage(e.selected)}
|
onPageChange={e => this.changePage(e.selected)}
|
||||||
forcePage={page}
|
forcePage={page}
|
||||||
initialPage={page}
|
initialPage={page}
|
||||||
|
disableInitialCallback
|
||||||
containerClassName="pagination"
|
containerClassName="pagination"
|
||||||
/>
|
/>
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
|
|
|
@ -12,7 +12,7 @@ const WalletBalance = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="card card--section card--wallet-balance"
|
className="card card--section card--wallet-balance"
|
||||||
style={{ backgroundImage: `url(${BalanceBackground})` }}
|
style={{ backgroundImage: `url(/${BalanceBackground})` }}
|
||||||
>
|
>
|
||||||
<header className="card__header">
|
<header className="card__header">
|
||||||
<h2 className="card__title">{__('Balance')}</h2>
|
<h2 className="card__title">{__('Balance')}</h2>
|
||||||
|
|
|
@ -236,7 +236,7 @@ const init = () => {
|
||||||
window.sessionStorage.setItem('loaded', 'y'); // once we've made it here once per session, we don't need to show splash again
|
window.sessionStorage.setItem('loaded', 'y'); // once we've made it here once per session, we don't need to show splash again
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
// app.store.dispatch(doDaemonReady());
|
app.store.dispatch(doDaemonReady());
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doHideModal } from 'redux/actions/app';
|
import { doHideModal } from 'redux/actions/app';
|
||||||
import { selectPhoneToVerify, selectUser } from 'lbryinc';
|
import { selectPhoneToVerify, selectUser } from 'lbryinc';
|
||||||
|
import { navigate } from '@reach/router';
|
||||||
import ModalPhoneCollection from './view';
|
import ModalPhoneCollection from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -11,6 +12,7 @@ const select = state => ({
|
||||||
const perform = dispatch => () => ({
|
const perform = dispatch => () => ({
|
||||||
closeModal: () => {
|
closeModal: () => {
|
||||||
dispatch(doHideModal());
|
dispatch(doHideModal());
|
||||||
|
navigate('/$/rewards');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { selectPathAfterAuth } from 'lbry-redux';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
selectAuthenticationIsPending,
|
selectAuthenticationIsPending,
|
||||||
|
@ -16,7 +15,6 @@ const select = state => ({
|
||||||
selectUserIsPending(state) ||
|
selectUserIsPending(state) ||
|
||||||
selectIdentityVerifyIsPending(state),
|
selectIdentityVerifyIsPending(state),
|
||||||
email: selectEmailToVerify(state),
|
email: selectEmailToVerify(state),
|
||||||
pathAfterAuth: selectPathAfterAuth(state),
|
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
isVerificationCandidate: selectUserIsVerificationCandidate(state),
|
isVerificationCandidate: selectUserIsVerificationCandidate(state),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import type { UrlLocation } from 'types/location';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import BusyIndicator from 'component/common/busy-indicator';
|
import BusyIndicator from 'component/common/busy-indicator';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
@ -12,6 +13,7 @@ type Props = {
|
||||||
isPending: boolean,
|
isPending: boolean,
|
||||||
email: string,
|
email: string,
|
||||||
pathAfterAuth: string,
|
pathAfterAuth: string,
|
||||||
|
location: UrlLocation,
|
||||||
user: ?{
|
user: ?{
|
||||||
has_verified_email: boolean,
|
has_verified_email: boolean,
|
||||||
is_reward_approved: boolean,
|
is_reward_approved: boolean,
|
||||||
|
@ -29,14 +31,18 @@ class AuthPage extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateIfAuthenticated = (props: Props) => {
|
navigateIfAuthenticated = (props: Props) => {
|
||||||
const { isPending, user } = props;
|
const { isPending, user, location } = props;
|
||||||
if (
|
if (
|
||||||
!isPending &&
|
!isPending &&
|
||||||
user &&
|
user &&
|
||||||
user.has_verified_email &&
|
user.has_verified_email &&
|
||||||
(user.is_reward_approved || user.is_identity_verified)
|
(user.is_reward_approved || user.is_identity_verified)
|
||||||
) {
|
) {
|
||||||
navigate('/');
|
const { search } = location;
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const redirectTo = urlParams.get('redirect');
|
||||||
|
const path = redirectTo ? `/$/${redirectTo}` : '/';
|
||||||
|
navigate(path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ const select = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris(true)),
|
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()),
|
||||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
fetchRewards: () => dispatch(doRewardList()),
|
fetchRewards: () => dispatch(doRewardList()),
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
selectUser,
|
selectUser,
|
||||||
doRewardList,
|
doRewardList,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { doAuthNavigate } from 'redux/actions/navigation';
|
import { navigate } from '@reach/router';
|
||||||
import { selectDaemonSettings } from 'redux/selectors/settings';
|
import { selectDaemonSettings } from 'redux/selectors/settings';
|
||||||
import RewardsPage from './view';
|
import RewardsPage from './view';
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ const select = state => ({
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchRewards: () => dispatch(doRewardList()),
|
fetchRewards: () => dispatch(doRewardList()),
|
||||||
doAuth: () => {
|
doAuth: () => {
|
||||||
dispatch(doAuthNavigate('/rewards'));
|
navigate('/$/auth?redirect=rewards');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import ShowPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
// claimName and claimId come from the url `lbry.tv/{claimName}/{claimId}"
|
// claimName and claimId come from the url `lbry.tv/{claimName}/{claimId}"
|
||||||
console.log('props', props);
|
|
||||||
const uri = buildURI({ contentName: props.claimName, claimId: props.claimId });
|
const uri = buildURI({ contentName: props.claimName, claimId: props.claimId });
|
||||||
return {
|
return {
|
||||||
claim: makeSelectClaimForUri(uri)(state),
|
claim: makeSelectClaimForUri(uri)(state),
|
||||||
|
|
|
@ -9,7 +9,7 @@ class UserHistoryPage extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<UserHistory />
|
<UserHistory {...this.props} />
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,6 +317,7 @@ export function doDaemonReady() {
|
||||||
dispatch({ type: ACTIONS.DAEMON_READY });
|
dispatch({ type: ACTIONS.DAEMON_READY });
|
||||||
dispatch(doFetchDaemonSettings());
|
dispatch(doFetchDaemonSettings());
|
||||||
dispatch(doBalanceSubscribe());
|
dispatch(doBalanceSubscribe());
|
||||||
|
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
dispatch(doFetchFileInfosAndPublishedClaims());
|
dispatch(doFetchFileInfosAndPublishedClaims());
|
||||||
if (!selectIsUpgradeSkipped(state)) {
|
if (!selectIsUpgradeSkipped(state)) {
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
// at smaller screen widths
|
// at smaller screen widths
|
||||||
|
|
||||||
@media (min-width: 601px) {
|
@media (min-width: 601px) {
|
||||||
// @if TARGET='app'
|
/* @if TARGET='app' */
|
||||||
width: 250px;
|
width: 250px;
|
||||||
// @endif
|
/* @endif */
|
||||||
// @if TARGET='web'
|
/* @if TARGET='web' */
|
||||||
width: 170px;
|
// width: 170px;
|
||||||
// @endif
|
/* @endif */
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
@ -43,9 +43,9 @@
|
||||||
|
|
||||||
.header__navigation-item {
|
.header__navigation-item {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
background-position: center;
|
display: flex;
|
||||||
background-repeat: no-repeat;
|
justify-content: center;
|
||||||
background-size: 50%;
|
align-items: center;
|
||||||
|
|
||||||
&:not(:disabled):hover {
|
&:not(:disabled):hover {
|
||||||
background-color: $lbry-gray-1;
|
background-color: $lbry-gray-1;
|
||||||
|
@ -68,73 +68,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__navigation-item--lbry {
|
|
||||||
flex: 1;
|
|
||||||
font-weight: 800;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding-left: var(--spacing-vertical-large);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__navigation-item--back {
|
|
||||||
html[data-mode='dark'] & {
|
|
||||||
svg {
|
|
||||||
stroke: $lbry-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__navigation-item--forward {
|
|
||||||
html[data-mode='dark'] & {
|
|
||||||
svg {
|
|
||||||
stroke: $lbry-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__navigation-item--home {
|
|
||||||
html[data-mode='dark'] & {
|
|
||||||
svg {
|
|
||||||
stroke: $lbry-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__navigation-item--menu {
|
|
||||||
html[data-mode='dark'] & {
|
|
||||||
svg {
|
|
||||||
stroke: $lbry-white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This menu button does not need to be seen
|
|
||||||
// at larger screen widths
|
|
||||||
|
|
||||||
@media (min-width: 601px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header__navigation-item--back,
|
.header__navigation-item--back,
|
||||||
.header__navigation-item--forward,
|
.header__navigation-item--forward,
|
||||||
.header__navigation-item--home,
|
.header__navigation-item--home,
|
||||||
.header__navigation-item--menu {
|
.header__navigation-item--menu {
|
||||||
width: var(--header-height);
|
width: var(--header-height);
|
||||||
|
svg {
|
||||||
|
&:only-child {
|
||||||
|
// Header icons are a little different because they are larger
|
||||||
|
top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
stroke: $lbry-white;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Currently not used
|
.header__navigation-item--lbry {
|
||||||
// .header__navigation-item--publish,
|
flex: 1;
|
||||||
// .header__navigation-item--wallet {
|
font-weight: 800;
|
||||||
// // Publish and Wallet links are collapsed
|
font-size: 1.2rem;
|
||||||
// // into a menu at smaller screen widths
|
padding-left: var(--spacing-vertical-large);
|
||||||
//
|
|
||||||
// @media (max-width: 600px) {
|
.button__content {
|
||||||
// display: none;
|
text-align: left;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Publish button
|
// Publish button
|
||||||
.header__navigation-item--right-action {
|
.header__navigation-item--right-action {
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.button__content {
|
.button__content {
|
||||||
|
@ -170,3 +133,23 @@
|
||||||
border-color: $lbry-gray-5;
|
border-color: $lbry-gray-5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__navigation-item--menu {
|
||||||
|
@media (min-width: 601px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide links that will live in the menu bar
|
||||||
|
@media (max-width: 601px) {
|
||||||
|
.header__navigation-item--back,
|
||||||
|
.header__navigation-item--forward,
|
||||||
|
.header__navigation-item--home,
|
||||||
|
.header__navigation-item--right-action {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__navigation:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.media__uri {
|
.media__uri {
|
||||||
font-size: 1.5rem;
|
font-size: 1.1rem;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
.help {
|
.help {
|
||||||
font-size: 0.9em;
|
font-size: 1rem;
|
||||||
background-color: rgba($lbry-blue-1, 0.1);
|
background-color: rgba($lbry-blue-1, 0.1);
|
||||||
color: $lbry-gray-5;
|
color: $lbry-gray-5;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -24,7 +24,7 @@ let mainConfig = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.(jsx?$|s?css$)/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'preprocess-loader',
|
loader: 'preprocess-loader',
|
||||||
|
|
|
@ -20,7 +20,7 @@ const webConfig = {
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.jsx?$/,
|
test: /\.(jsx?$|s?css$)/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: 'preprocess-loader',
|
loader: 'preprocess-loader',
|
||||||
|
|
Loading…
Reference in a new issue