mirror of https://github.com/certusone/oyster.git
feat: mobile layout
This commit is contained in:
parent
bbeaf5c420
commit
d4a7ebd19a
|
@ -1,13 +1,10 @@
|
|||
import React from 'react';
|
||||
import './../../App.less';
|
||||
import { Divider, Layout, Menu } from 'antd';
|
||||
import { Breadcrumb, Layout } from 'antd';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import { LABELS } from '../../constants';
|
||||
import config from './../../../package.json';
|
||||
import { contexts, components } from '@oyster/common';
|
||||
import { NewProposalMenuItem } from '../../views/proposal/new';
|
||||
import { RegisterGovernanceMenuItem } from '../../views/governance/register';
|
||||
import { Content, Header } from 'antd/lib/layout/layout';
|
||||
import Logo from './dark-horizontal-combined-rainbow.inline.svg';
|
||||
|
||||
|
@ -18,16 +15,30 @@ export const AppLayout = React.memo((props: any) => {
|
|||
const { env } = useConnectionConfig();
|
||||
const location = useLocation();
|
||||
|
||||
const paths: { [key: string]: string } = {
|
||||
'/': '1',
|
||||
'/dashboard': '2',
|
||||
const breadcrumbNameMap: any = {
|
||||
'/governance': 'Governance',
|
||||
'/apps/1': 'Application1',
|
||||
'/apps/2': 'Application2',
|
||||
'/apps/1/detail': 'Detail',
|
||||
'/apps/2/detail': 'Detail',
|
||||
};
|
||||
|
||||
const current =
|
||||
[...Object.keys(paths)].find(key => location.pathname.startsWith(key)) ||
|
||||
'';
|
||||
const defaultKey = paths[current] || '1';
|
||||
const theme = 'dark';
|
||||
const pathSnippets = location.pathname.split('/').filter(i => i);
|
||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
||||
return (
|
||||
<Breadcrumb.Item key={url}>
|
||||
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
});
|
||||
const breadcrumbItems = [
|
||||
<Breadcrumb.Item key="home">
|
||||
<Link to="/">Home</Link>
|
||||
</Breadcrumb.Item>,
|
||||
].concat(extraBreadcrumbItems);
|
||||
|
||||
// TODO: add breadcrumb
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
|
@ -46,7 +57,8 @@ export const AppLayout = React.memo((props: any) => {
|
|||
useWalletBadge={true}
|
||||
/>
|
||||
</Header>
|
||||
<Content style={{ padding: '0 50px', flexDirection: 'column' }}>
|
||||
<Content>
|
||||
{/* <Breadcrumb>{breadcrumbItems}</Breadcrumb> */}
|
||||
{props.children}
|
||||
</Content>
|
||||
</Layout>
|
||||
|
|
|
@ -23,7 +23,7 @@ export function Routes() {
|
|||
<Switch>
|
||||
<Route exact path="/" component={() => <HomeView />} />
|
||||
<Route path="/proposal/:id" children={<ProposalView />} />
|
||||
<Route path="/proposals/:id" children={<ProposalsView />} />
|
||||
<Route path="/governance/:id" children={<ProposalsView />} />
|
||||
|
||||
<Route
|
||||
exact
|
||||
|
|
|
@ -1,40 +1,64 @@
|
|||
import { Col, List, Row } from 'antd';
|
||||
import { Badge, Col, List, Row, Statistic } from 'antd';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useProposals } from '../../contexts/proposals';
|
||||
import './style.less'; // Don't remove this line, it will break dark mode if you do due to weird transpiling conditions
|
||||
import { TokenIcon } from '@oyster/common';
|
||||
import { TokenIcon, useConnectionConfig, useWallet } from '@oyster/common';
|
||||
import { Background } from './../../components/Background';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { RegisterGovernanceMenuItem } from '../governance/register';
|
||||
import { TimelockStateStatus } from '../../models/timelock';
|
||||
|
||||
export const HomeView = () => {
|
||||
const history = useHistory();
|
||||
const { configs } = useProposals();
|
||||
const [page, setPage] = useState(0);
|
||||
const { configs, proposals } = useProposals();
|
||||
const { connected } = useWallet();
|
||||
const listData = useMemo(() => {
|
||||
const newListData: any[] = [];
|
||||
|
||||
Object.keys(configs).forEach(key => {
|
||||
const config = configs[key];
|
||||
Object.keys(configs).forEach(configKey => {
|
||||
const config = configs[configKey];
|
||||
const mint = config.info.governanceMint.toBase58();
|
||||
|
||||
const proposalCount = Object.keys(proposals).reduce((acc, proposalKey) => {
|
||||
let proposal = proposals[proposalKey];
|
||||
if(proposal.info.config.toBase58() == configKey) {
|
||||
acc.active = acc.active + (
|
||||
proposal.info.state.status === TimelockStateStatus.Voting ||
|
||||
proposal.info.state.status === TimelockStateStatus.Draft ? 1 : 0);
|
||||
|
||||
acc.total = acc.total +1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {
|
||||
active: 0,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
|
||||
newListData.push({
|
||||
href: '/proposals/' + key,
|
||||
href: '/governance/' + configKey,
|
||||
title: config.info.name,
|
||||
badge: <TokenIcon mintAddress={config.info.governanceMint} size={40} />,
|
||||
badge: <Badge count={proposalCount.active}>
|
||||
<TokenIcon mintAddress={mint} size={40} />
|
||||
</Badge>,
|
||||
proposalCount,
|
||||
configKey,
|
||||
config,
|
||||
});
|
||||
});
|
||||
return newListData;
|
||||
}, [configs]);
|
||||
}, [configs, proposals]);
|
||||
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col flex="auto">
|
||||
<>
|
||||
<Background />
|
||||
<Row>
|
||||
<Col flex="auto" xxl={15} xs={24} className="governance-container">
|
||||
<div className="governance-title">
|
||||
<h1>Governance</h1>
|
||||
<RegisterGovernanceMenuItem style={{ marginLeft: 'auto', marginRight: 0 }} />
|
||||
<RegisterGovernanceMenuItem style={{ marginLeft: 'auto', marginRight: 0 }} disabled={!connected} />
|
||||
</div>
|
||||
<List
|
||||
itemLayout="vertical"
|
||||
|
@ -42,17 +66,27 @@ export const HomeView = () => {
|
|||
loading={listData.length === 0}
|
||||
pagination={false}
|
||||
dataSource={listData}
|
||||
|
||||
renderItem={item => (
|
||||
<List.Item key={item.title} className="governance-item" onClick={() => history.push(item.href)}>
|
||||
<List.Item key={item.title} className="governance-item" onClick={() => history.push(item.href)}
|
||||
extra={
|
||||
<>
|
||||
<Statistic title="Proposals" value={item.proposalCount.total} />
|
||||
</>
|
||||
}>
|
||||
<List.Item.Meta
|
||||
avatar={item.badge}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
/>
|
||||
>
|
||||
</List.Item.Meta>
|
||||
|
||||
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
@import '~antd/dist/antd.dark.less';
|
||||
|
||||
.governance-container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.governance-title {
|
||||
font-family: "FF Oxide Solid";
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0px;
|
||||
position: absolute;
|
||||
top: 240px;
|
||||
left: calc(20% + 10px);
|
||||
top: -40px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
right: calc(20% + 10px);
|
||||
width: 100%;
|
||||
padding: 0px 10px;
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
|
@ -24,13 +30,32 @@
|
|||
background-color: #202020;
|
||||
padding: 0px !important;
|
||||
border-radius: 15px;
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
background-color: #252425;
|
||||
border-radius: 15px;
|
||||
.ant-badge-count {
|
||||
top: 8px !important;
|
||||
right: 12px !important;
|
||||
}
|
||||
|
||||
.ant-list-item-extra {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
margin-left: 0px !important;
|
||||
margin-right: auto;
|
||||
|
||||
.ant-statistic {
|
||||
margin-top: 5px;
|
||||
padding: 0px 20px;
|
||||
|
||||
.ant-statistic-content {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list-item-main {
|
||||
flex: 0 !important;
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.ant-list-item-meta {
|
||||
|
@ -46,3 +71,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.governance-item:hover {
|
||||
cursor: pointer;
|
||||
background-color: #252425;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,9 @@ import { Col, List, Row } from 'antd';
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import { useConfig, useProposals } from '../../contexts/proposals';
|
||||
import './style.less'; // Don't remove this line, it will break dark mode if you do due to weird transpiling conditions
|
||||
import { StateBadge, StateBadgeRibbon } from '../../components/Proposal/StateBadge';
|
||||
import { urlRegex } from '../proposal';
|
||||
import { StateBadge } from '../../components/Proposal/StateBadge';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { TokenIcon } from '@oyster/common';
|
||||
import { TokenIcon, useConnectionConfig, useWallet } from '@oyster/common';
|
||||
import { NewProposalMenuItem } from '../proposal/new';
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
|
@ -15,6 +14,13 @@ export const ProposalsView = () => {
|
|||
const { proposals } = useProposals();
|
||||
const config = useConfig(id);
|
||||
const [page, setPage] = useState(0);
|
||||
const { tokenMap } = useConnectionConfig();
|
||||
const { connected } = useWallet();
|
||||
const token = tokenMap.get(config?.info.governanceMint.toBase58() || '') as any;
|
||||
const tokenBackground = token?.extensions?.background || 'https://solana.com/static/8c151e179d2d7e80255bdae6563209f2/6833b/validators.webp';
|
||||
|
||||
const mint = config?.info.governanceMint.toBase58() || '';
|
||||
|
||||
const listData = useMemo(() => {
|
||||
const newListData: any[] = [];
|
||||
|
||||
|
@ -24,21 +30,12 @@ export const ProposalsView = () => {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
newListData.push({
|
||||
href: '/proposal/' + key,
|
||||
title: proposal.info.state.name,
|
||||
badge: <TokenIcon mintAddress={config?.info.governanceMint} size={30} />,
|
||||
badge: <TokenIcon mintAddress={mint} size={30} />,
|
||||
status: proposal.info.state.status,
|
||||
proposal,
|
||||
description: proposal.info.state.descLink.match(urlRegex) ? (
|
||||
<a href={proposal.info.state.descLink} target={'_blank'}>
|
||||
Link to markdown
|
||||
</a>
|
||||
) : (
|
||||
proposal.info.state.descLink
|
||||
),
|
||||
});
|
||||
});
|
||||
return newListData;
|
||||
|
@ -46,13 +43,18 @@ export const ProposalsView = () => {
|
|||
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col flex="auto">
|
||||
<Row style={{ background: `url(${tokenBackground})`, height: '100%' }}>
|
||||
<Col flex="auto" xxl={15} xs={24} className="proposals-container">
|
||||
<div className="proposals-header">
|
||||
<TokenIcon mintAddress={config?.info.governanceMint} size={40} />
|
||||
<TokenIcon mintAddress={config?.info.governanceMint} size={60} style={{ marginRight: 20 }} />
|
||||
<div>
|
||||
<h1>{config?.info.name}</h1>
|
||||
<a href={tokenMap.get(mint)?.extensions?.website} target="_blank">
|
||||
{tokenMap.get(mint)?.extensions?.website}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<NewProposalMenuItem className="proposals-new-btn" />
|
||||
<NewProposalMenuItem className="proposals-new-btn" disabled={!connected} />
|
||||
</div>
|
||||
<List
|
||||
itemLayout="vertical"
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
@import '~antd/dist/antd.dark.less';
|
||||
|
||||
.proposals-container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.proposals-header {
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0px 10px;
|
||||
text-align: left;
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
|
@ -22,8 +28,6 @@
|
|||
background-color: #202020;
|
||||
padding: 0px !important;
|
||||
border-radius: 15px;
|
||||
margin-left: 20%;
|
||||
margin-right: 20%;
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
|
|
Loading…
Reference in New Issue