feat: implement dashboard

This commit is contained in:
jinhojang6 2024-09-12 03:07:46 +09:00
parent a8d17fa3be
commit 4a7bcfebde
No known key found for this signature in database
GPG Key ID: 1762F21FE8B543F8
41 changed files with 1177 additions and 25 deletions

16
public/assets/btc.svg Normal file
View File

@ -0,0 +1,16 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2141_7)">
<path d="M7.88004 7.09925C8.06163 7.1019 8.24193 7.06843 8.41046 7.00076C8.57899 6.9331 8.73239 6.83261 8.86173 6.70513C8.99107 6.57765 9.09378 6.42572 9.16387 6.25819C9.23397 6.09066 9.27006 5.91086 9.27004 5.72925V5.72925C9.27004 5.36591 9.1257 5.01744 8.86878 4.76052C8.61185 4.50359 8.26339 4.35925 7.90004 4.35925H5.79004C5.65743 4.35925 5.53025 4.41193 5.43649 4.5057C5.34272 4.59947 5.29004 4.72664 5.29004 4.85925V9.85925C5.29004 9.99186 5.34272 10.119 5.43649 10.2128C5.53025 10.3066 5.65743 10.3593 5.79004 10.3593H7.88004C8.30969 10.3593 8.72174 10.1886 9.02555 9.88477C9.32936 9.58096 9.50004 9.1689 9.50004 8.73925C9.50004 8.3096 9.32936 7.89755 9.02555 7.59374C8.72174 7.28993 8.30969 7.11925 7.88004 7.11925V7.09925Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.90004 7.09924H5.29004" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.23999 4.35921V3.43921" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.78003 4.35921V3.43921" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.23999 11.4392V10.3392" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.78003 11.4392V10.3392" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 13.9392C10.5899 13.9392 13.5 11.0291 13.5 7.43921C13.5 3.84936 10.5899 0.939209 7 0.939209C3.41015 0.939209 0.5 3.84936 0.5 7.43921C0.5 11.0291 3.41015 13.9392 7 13.9392Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_2141_7">
<rect width="14" height="14" fill="white" transform="translate(0 0.439209)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-down">
<path id="icon" d="M10.5001 5.66137L9.67762 4.83887L7.00012 7.51053L4.32262 4.83887L3.50012 5.66137L7.00012 9.16137L10.5001 5.66137Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View File

@ -0,0 +1,5 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="chevron-right">
<path id="Vector" d="M7.34999 7L4.66666 4.31667L5.48332 3.5L8.98332 7L5.48332 10.5L4.66666 9.68333L7.34999 7Z" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 257 B

5
public/assets/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

13
public/assets/pinned.svg Normal file
View File

@ -0,0 +1,13 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="27" height="27" fill="white"/>
<rect x="0.5" y="0.5" width="27" height="27" stroke="white"/>
<g clip-path="url(#clip0_2085_1139)">
<path d="M15.8351 11.8641L16.4681 8.92815C16.4955 8.81649 16.4975 8.70013 16.4742 8.58757C16.4508 8.47501 16.4025 8.36909 16.333 8.27756C16.2634 8.18603 16.1743 8.1112 16.0721 8.05853C15.9699 8.00586 15.8572 7.97669 15.7423 7.97315H12.2388C12.1239 7.97669 12.0112 8.00586 11.909 8.05853C11.8069 8.1112 11.7177 8.18603 11.6482 8.27756C11.5786 8.36909 11.5304 8.47501 11.507 8.58757C11.4836 8.70013 11.4857 8.81649 11.513 8.92815L12.0969 11.8696L10.1924 14.8656C10.1499 14.9352 10.1266 15.0148 10.1248 15.0963C10.123 15.1778 10.1428 15.2584 10.1821 15.3298C10.2214 15.4013 10.2789 15.4611 10.3487 15.5031C10.4186 15.5452 10.4983 15.5681 10.5798 15.5695L17.2976 15.575C17.3785 15.571 17.4571 15.5461 17.5256 15.5028C17.5941 15.4596 17.6503 15.3993 17.6887 15.328C17.7271 15.2567 17.7464 15.1766 17.7449 15.0956C17.7433 15.0146 17.7208 14.9354 17.6796 14.8656L15.8351 11.8641Z" fill="black" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.9741 15.6132L13.9905 20.0936" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_2085_1139">
<rect width="14" height="14" fill="white" transform="translate(7 7)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

3
public/assets/plus.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6667 7.58325H7.58337V11.6666H6.41671V7.58325H2.33337V6.41658H6.41671V2.33325H7.58337V6.41658H11.6667V7.58325Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 242 B

View File

@ -0,0 +1,4 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 3.93921H13.5V7.93921" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 3.93921L7.85 9.58921C7.75654 9.68082 7.63088 9.73214 7.5 9.73214C7.36912 9.73214 7.24346 9.68082 7.15 9.58921L4.85 7.28921C4.75654 7.1976 4.63088 7.14628 4.5 7.14628C4.36912 7.14628 4.24346 7.1976 4.15 7.28921L0.5 10.9392" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="27" height="27" stroke="white"/>
<path d="M16.1049 14.5038L18.6286 12.8753C18.7269 12.8157 18.8106 12.7349 18.8736 12.6388C18.9367 12.5427 18.9775 12.4336 18.993 12.3197C19.0086 12.2058 18.9984 12.0899 18.9634 11.9804C18.9284 11.8709 18.8694 11.7706 18.7906 11.6868L16.3133 9.20948C16.2295 9.13073 16.1292 9.07169 16.0197 9.03667C15.9102 9.00166 15.7943 8.99154 15.6804 9.00708C15.5665 9.02261 15.4575 9.0634 15.3613 9.12645C15.2652 9.18951 15.1844 9.27325 15.1248 9.37155L13.4578 11.8643L9.99254 12.6361C9.9133 12.6553 9.84053 12.6951 9.78162 12.7515C9.7227 12.8079 9.67971 12.8788 9.657 12.9571C9.63428 13.0354 9.63266 13.1184 9.65227 13.1975C9.67189 13.2767 9.71207 13.3492 9.76873 13.4079L14.5151 18.1619C14.5751 18.2163 14.6482 18.2542 14.7273 18.2721C14.8063 18.2899 14.8886 18.2871 14.9662 18.2638C15.0438 18.2405 15.1141 18.1976 15.1703 18.1392C15.2265 18.0808 15.2666 18.0089 15.2868 17.9304L16.1049 14.5038Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.138 15.8389L8.98145 19.0185" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 954 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 954 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 954 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 954 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

View File

@ -0,0 +1,283 @@
import { breakpoints } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import React from 'react'
interface Operator {
id: string
image: string
name: string
pointsPerHour: number
isStaked: boolean
isPinned: boolean
}
interface OperatorGridProps {}
const operators: Operator[] = [
{
id: '1234',
image: '/dashboard/mock/operators/1.gif',
name: 'OP1234',
pointsPerHour: 304,
isStaked: false,
isPinned: false,
},
{
id: '1235',
image: '/dashboard/mock/operators/2.gif',
name: 'OP1235',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1236',
image: '/dashboard/mock/operators/3.gif',
name: 'OP1236',
pointsPerHour: 304,
isStaked: true,
isPinned: true,
},
{
id: '1237',
image: '/dashboard/mock/operators/4.gif',
name: 'OP1237',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1238',
image: '/dashboard/mock/operators/5.gif',
name: 'OP1238',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1239',
image: '/dashboard/mock/operators/6.gif',
name: 'OP1239',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1240',
image: '/dashboard/mock/operators/7.gif',
name: 'OP1240',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
]
const OperatorGrid: React.FC<OperatorGridProps> = () => {
return (
<StyledOperatorGrid>
<Header>
<Title>Operators</Title>
<Controls>
<FilterDropdown>
<p>Filter</p>
<img src="/assets/chevron-down.svg" alt="chevron down" />
</FilterDropdown>
<IconButton>
<img src="/assets/plus.svg" alt="Settings" />
</IconButton>
</Controls>
</Header>
<Stats>
<Stat>
<Label>Total Operators</Label>
<Value>7</Value>
</Stat>
<Stat>
<Label>Staked</Label>
<Value>1</Value>
</Stat>
<Stat>
<Label>Total Points</Label>
<Value>4,278</Value>
</Stat>
<Stat>
<Label>Points/Hr</Label>
<Value>912</Value>
</Stat>
</Stats>
<Grid>
{operators.map((operator) => (
<OperatorCard key={operator.id}>
<OperatorImage src={operator.image} alt={operator.name} />
<OperatorInfo>
<OperatorName>{operator.name}</OperatorName>
<PointsPerHour>
<Label>Per Hour</Label>
<Value>{operator.pointsPerHour} rp</Value>
</PointsPerHour>
</OperatorInfo>
<Actions>
<ActionButton isStaked={operator.isStaked}>
{operator.isStaked ? 'Unstake' : 'Stake'}
</ActionButton>
<IconButton>
{operator.isPinned ? (
<img src="/assets/pinned.svg" alt="Pinned" />
) : (
<img src="/assets/unpinned.svg" alt="Unpinned" />
)}
</IconButton>
</Actions>
</OperatorCard>
))}
</Grid>
</StyledOperatorGrid>
)
}
const StyledOperatorGrid = styled.section`
margin-top: 94px;
@media (max-width: ${breakpoints.md}px) {
margin-top: 40px;
}
`
const Header = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
`
const Title = styled.h2`
font-weight: 400;
font-size: 24px;
line-height: 1;
margin: 0;
`
const Controls = styled.div`
display: flex;
gap: 8px;
`
const FilterDropdown = styled.div`
background-color: transparent;
width: 128px;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid rgb(var(--lsd-border-primary));
color: rgb(var(--lsd-text-primary));
font-weight: 400;
font-size: 12px;
line-height: 1;
padding-left: 12px;
height: 28px;
cursor: pointer;
img {
padding: 8px;
}
`
const IconButton = styled.button`
background-color: transparent;
border-left: none;
width: 28px;
height: 28px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgb(var(--lsd-border-primary));
cursor: pointer;
height: 28px;
`
const Stats = styled.div`
display: flex;
justify-content: space-between;
margin-bottom: 16px;
`
const Stat = styled.div`
background-color: var(--grey-900);
padding: 16px 8px;
flex: 1;
`
const Label = styled.div`
font-weight: 400;
font-size: 14px;
line-height: 1;
`
const Value = styled.div`
font-weight: 400;
font-size: 14px;
line-height: 1;
margin-top: 8px;
`
const Grid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
`
const OperatorCard = styled.div`
background-color: var(--grey-900);
display: flex;
flex-direction: column;
`
const OperatorImage = styled.img`
width: 100%;
aspect-ratio: 1;
object-fit: cover;
`
const OperatorInfo = styled.div`
padding: 16px;
`
const OperatorName = styled.div`
font-weight: 400;
font-size: 14px;
line-height: 1;
`
const PointsPerHour = styled.div`
display: flex;
justify-content: space-between;
margin-top: 8px;
align-items: center;
`
const Actions = styled.div`
display: flex;
padding: 16px;
`
const ActionButton = styled.button<{ isStaked: boolean }>`
flex: 1;
background-color: ${(props) =>
props.isStaked ? 'transparent' : 'rgb(var(--lsd-surface-secondary))'};
color: ${(props) =>
props.isStaked
? 'rgb(var(--lsd-text-primary))'
: 'rgb(var(--lsd-text-secondary))'};
border: 1px solid rgb(var(--lsd-border-primary));
border-right: none;
border-radius: 0;
font-weight: 400;
font-size: 12px;
line-height: 1;
padding: 6px 12px;
cursor: pointer;
`
export default OperatorGrid

View File

@ -0,0 +1 @@
export { default as OperatorGrid } from './OperatorGrid'

View File

@ -0,0 +1,172 @@
import styled from '@emotion/styled'
import React from 'react'
interface OperatorPanelProps {}
const TEMP_BADGES = [
{
id: 1,
image: '/dashboard/badges/badge-1.svg',
},
{
id: 2,
image: '/dashboard/badges/badge-2.svg',
},
{
id: 3,
image: '/dashboard/badges/badge-3.svg',
},
{
id: 4,
image: '/dashboard/badges/badge-4.svg',
},
{
id: 5,
image: '/dashboard/badges/badge-5.svg',
},
]
const OperatorPanel: React.FC<OperatorPanelProps> = () => {
return (
<StyledPanel>
<Profile>
<OperatorImage
src="/dashboard/mock/operators/pinned.gif"
alt="Operator"
/>
<OperatorInfo>
<OperatorType>Operator</OperatorType>
<OperatorName>Memetic #1234</OperatorName>
</OperatorInfo>
</Profile>
<InfoRow>
<Label>Archetype</Label>
<Value>Memetic</Value>
</InfoRow>
<CallSignContainer>
<InfoRow>
<Label>Callsign</Label>
<Value>RagingBull</Value>
</InfoRow>
</CallSignContainer>
<ProfileInfo>
<InfoRow>
<Label>OP Number</Label>
<Value>#214</Value>
</InfoRow>
<BadgesSection>
<BadgeTitle>
<Label>Badges</Label>
<BadgeIcon src="/assets/chevron-right.svg" alt="Badge info" />
</BadgeTitle>
<BadgeList>
{TEMP_BADGES.map((badge) => (
<Badge
key={badge.id}
src={badge.image}
alt={`Badge ${badge.id}`}
/>
))}
</BadgeList>
</BadgesSection>
</ProfileInfo>
</StyledPanel>
)
}
const StyledPanel = styled.section`
font-weight: 400;
font-size: 14px;
line-height: 1;
`
const OperatorImage = styled.img`
width: 100%;
aspect-ratio: 1;
object-fit: cover;
`
const OperatorInfo = styled.div`
display: flex;
justify-content: space-between;
padding: 16px 8px;
margin-bottom: 2px;
`
const OperatorType = styled.span``
const OperatorName = styled.span``
const InfoColumn = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
`
const InfoRow = styled.div`
display: flex;
justify-content: space-between;
padding: 16px 8px;
background-color: var(--grey-900);
`
const Profile = styled.div`
background-color: var(--grey-900);
padding: 8px;
margin-bottom: 2px;
`
const ProfileInfo = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
`
const CallSignContainer = styled.div`
margin-top: 24px;
margin-bottom: 2px;
`
const Label = styled.span``
const Value = styled.span`
text-align: right;
`
const BadgesSection = styled.div`
display: flex;
flex-direction: column;
background-color: var(--grey-900);
padding: 16px 8px;
`
const BadgeTitle = styled.div`
display: flex;
align-items: center;
margin-bottom: 16px;
width: 100%;
`
const BadgeIcon = styled.img`
width: 14px;
height: 14px;
margin-left: auto;
`
const BadgeList = styled.div`
display: flex;
flex-wrap: wrap;
gap: 8px;
`
const Badge = styled.img`
width: 45px;
height: 42px;
object-fit: contain;
cursor: pointer;
`
export default OperatorPanel

View File

@ -0,0 +1 @@
export { default as OperatorPanel } from './OperatorPanel'

View File

@ -0,0 +1,181 @@
import styled from '@emotion/styled'
import React, { useEffect, useState } from 'react'
interface ProgressBarProps {
progress: number // Current progress of the epoch
claimPosition: number // Position where "claim period" should appear
}
const ProgressBar: React.FC<ProgressBarProps> = ({
progress = 0,
claimPosition = 60,
}) => {
const [timeRemaining, setTimeRemaining] = useState<string>('...')
useEffect(() => {
const dueDate = new Date('2024-12-31T23:59:59Z')
const updateRemainingTime = () => {
const now = new Date()
const timeDifference = dueDate.getTime() - now.getTime()
if (timeDifference <= 0) {
setTimeRemaining('Expired')
return
}
const seconds = Math.floor((timeDifference / 1000) % 60)
const minutes = Math.floor((timeDifference / 1000 / 60) % 60)
const hours = Math.floor((timeDifference / 1000 / 60 / 60) % 24)
const days = Math.floor(timeDifference / 1000 / 60 / 60 / 24) % 30
const months = Math.floor(timeDifference / 1000 / 60 / 60 / 24 / 30)
setTimeRemaining(
`${months}mo, ${days}days, ${hours}hrs, ${minutes}min, ${seconds}sec`,
)
}
const intervalId = setInterval(updateRemainingTime, 1000)
return () => clearInterval(intervalId)
}, [])
return (
<StyledProgressBar>
<ProgressHeader>
<EpochLabel>epoch 1</EpochLabel>
<EpochInfo>
<NextEpoch>epoch 2</NextEpoch>
</EpochInfo>
</ProgressHeader>
<ProgressTrack>
<ProgressFill width={progress} />
<ClaimPeriodWrapper position={claimPosition}>
<ClaimPeriod>claim period</ClaimPeriod>
<VerticalBar />
</ClaimPeriodWrapper>
</ProgressTrack>
<ProgressStats>
<Stat>Staking Rate: 100%</Stat>
<Stat>Bonus Reward: 20%</Stat>
</ProgressStats>
<ProgressFooter>
<TimeRemaining>
<Label>Time Remaining</Label>
<Value color="#fe740c" backgroundColor="#321504">
{timeRemaining}
</Value>
</TimeRemaining>
<EarnedReward>
<Label>Earned Epoch 1</Label>
<Value color="#F29AE9" backgroundColor="#320430">
200.12
</Value>
</EarnedReward>
</ProgressFooter>
</StyledProgressBar>
)
}
const StyledProgressBar = styled.section`
font-weight: 400;
font-size: 14px;
line-height: 1;
`
const ProgressHeader = styled.div`
display: flex;
justify-content: space-between;
`
const EpochLabel = styled.span`
background-color: rgb(var(--lsd-surface-secondary));
color: rgb(var(--lsd-text-secondary));
padding: 0 8px;
`
const EpochInfo = styled.div`
display: flex;
gap: 8px;
`
const NextEpoch = styled.span``
const ProgressTrack = styled.div`
position: relative;
height: 8px;
margin: 14px 0;
border-bottom: 1px solid rgb(var(--lsd-border-primary));
border-right: 1px solid rgb(var(--lsd-border-primary));
`
const ProgressFill = styled.div<{ width: number }>`
height: 100%;
width: ${(props) => props.width}%;
background-color: rgb(var(--lsd-surface-secondary));
`
const ClaimPeriodWrapper = styled.div<{ position: number }>`
position: absolute;
top: -14px;
left: ${(props) => props.position}%;
display: flex;
flex-direction: column;
`
const ClaimPeriod = styled.span`
background-color: rgb(var(--lsd-surface-secondary));
color: rgb(var(--lsd-text-secondary));
padding: 0 8px;
position: relative;
top: -8px;
`
const VerticalBar = styled.div`
width: 1px;
height: 8px;
background-color: white;
z-index: 1;
`
const ProgressStats = styled.div`
display: flex;
justify-content: space-between;
margin-bottom: 24px;
`
const Stat = styled.span``
const ProgressFooter = styled.div`
display: flex;
gap: 16px;
`
const TimeRemaining = styled.div`
border: 1px solid #321504;
display: flex;
align-items: center;
gap: 16px;
`
const EarnedReward = styled.div`
border: 1px solid #320430;
display: flex;
align-items: center;
gap: 16px;
`
const Label = styled.div`
font-size: 12px;
padding: 8px 16px;
`
const Value = styled.div<{ color: string; backgroundColor: string }>`
font-size: 12px;
color: ${(props) => props.color};
background-color: ${(props) => props.backgroundColor};
padding: 8px 16px;
`
export default ProgressBar

View File

@ -0,0 +1 @@
export { default as ProgressBar } from './ProgressBar'

View File

@ -0,0 +1,81 @@
import styled from '@emotion/styled'
import React from 'react'
import { Navbar } from '../Navbar'
interface NavbarProps {}
const Header: React.FC<NavbarProps> = () => {
return (
<Container>
<Logo src="/assets/logo.svg" alt="Logo" />
<Navbar />
<UserActions>
<WalletButton>
<WalletAddress>bc1qa...vehs9</WalletAddress>
<Icon src="/assets/btc.svg" alt="Wallet icon" />
</WalletButton>
<PointsButton>
<PointsValue>4,278</PointsValue>
<Icon src="/assets/stats-up.svg" alt="Points icon" />
</PointsButton>
</UserActions>
</Container>
)
}
const Container = styled.header`
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
`
const Logo = styled.img`
object-fit: contain;
width: 89px;
`
const UserActions = styled.div`
display: flex;
align-items: center;
`
const Button = styled.button`
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
height: 28px;
font-weight: 400;
font-size: 12px;
line-height: 1;
border: 1px solid rgb(var(--lsd-border-primary));
background: transparent;
color: rgb(var(--lsd-text-primary));
cursor: pointer;
`
const WalletButton = styled(Button)`
width: 140px;
`
const WalletAddress = styled.span`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
const PointsButton = styled(Button)`
width: 83px;
background-color: rgb(var(--lsd-surface-secondary));
color: rgb(var(--lsd-text-secondary));
`
const PointsValue = styled.span``
const Icon = styled.img`
width: 14px;
height: 14px;
`
export default Header

View File

@ -0,0 +1 @@
export { default as Header } from './Header'

View File

@ -0,0 +1,50 @@
import { breakpoints } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import React from 'react'
interface NavbarProps {}
const Navbar: React.FC<NavbarProps> = () => {
return (
<Navigation>
<NavItem active>Dashboard</NavItem>
<NavItem>Explore</NavItem>
<NavItem>Leaderboard</NavItem>
<NavItem>Docs</NavItem>
<NavItem>Auction</NavItem>
<NavItem>Roles</NavItem>
</Navigation>
)
}
const Navigation = styled.ul`
display: flex;
align-items: center;
gap: 37px;
list-style-type: none;
@media (max-width: ${breakpoints.md}px) {
position: static;
transform: none;
flex-wrap: wrap;
}
`
const NavItem = styled.li<{ active?: boolean }>`
font-weight: 400;
font-size: 14px;
line-height: 1;
cursor: pointer;
padding: 4px 0;
${(props) =>
props.active &&
`
&::before {
content: '>';
margin-right: 6px;
}
`}
`
export default Navbar

View File

@ -0,0 +1 @@
export { default as Navbar } from './Navbar'

View File

@ -1,7 +1,7 @@
export const uiConfigs = {
navbarRenderedHeight: 80,
navbarMobileHeight: 60,
maxContainerWidth: 1232,
maxContainerWidth: 1376,
articleRenderedMT: 45 * 2,
}

View File

@ -0,0 +1,68 @@
import { OperatorGrid } from '@/components/Dashboard/OperatorGrid'
import { OperatorPanel } from '@/components/Dashboard/OperatorPanel'
import { ProgressBar } from '@/components/Dashboard/ProgressBar'
import { breakpoints } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import React from 'react'
export type DashboardPageProps = React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
>
const DashboardContainer: React.FC<DashboardPageProps> = ({
children,
...props
}) => {
return (
<Container {...props}>
<Wrapper>
<LeftColumn>
<OperatorPanel />
</LeftColumn>
<RightColumn>
<ProgressBar progress={20} claimPosition={60} />
<OperatorGrid />
</RightColumn>
</Wrapper>
</Container>
)
}
export default DashboardContainer
const Container = styled.div`
@media (max-width: ${breakpoints.lg}px) {
margin-inline: 10px;
}
`
const Wrapper = styled.div`
display: grid;
grid-template-columns: repeat(24, 1fr);
gap: 0 16px;
margin-top: 80px;
width: 100%;
@media (max-width: ${breakpoints.md}px) {
grid-template-columns: 1fr;
margin-top: 40px;
}
`
const LeftColumn = styled.section`
grid-column: 1 / 6;
@media (max-width: ${breakpoints.md}px) {
grid-column: 1 / 2;
}
`
const RightColumn = styled.section`
grid-column: 8 / 23;
@media (max-width: ${breakpoints.md}px) {
grid-column: 1 / 2;
}
`

View File

@ -0,0 +1 @@
export { default as DashboardContainer } from './DashboardContainer'

View File

@ -52,13 +52,30 @@ export const useLSDTheme = () => {
},
},
palette: {
primary: '20, 0, 255',
primary: '0, 0, 0',
secondary: '255, 255, 255',
surface: {
primary: '0, 0, 0',
secondary: '255, 255, 255',
},
text: {
primary: '255, 255, 255',
secondary: '0, 0, 0',
tertiary: '0, 0, 0, 0.34',
},
border: {
primary: '255, 255, 255',
secondary: '0, 0, 0',
},
icon: {
primary: '255, 255, 255',
secondary: '0, 0, 0',
},
},
spacing: [],
typography: {},
typographyGlobal: {
genericFontFamily: genericFontFamily,
genericFontFamily: genericFontFamily as any,
},
}

View File

@ -0,0 +1,33 @@
import { Header } from '@/components/Header/Header'
import { breakpoints, uiConfigs } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import React, { PropsWithChildren } from 'react'
interface DashboardLayoutProps {}
const DashboardLayout: React.FC<DashboardLayoutProps> = (
props: PropsWithChildren,
) => {
return (
<Container>
<Header />
<main>{props.children}</main>
</Container>
)
}
const Container = styled.div`
display: flex;
flex-direction: column;
overflow: hidden;
max-width: ${uiConfigs.maxContainerWidth}px;
margin: 0 auto;
padding: 24px;
@media (max-width: ${breakpoints.md}px) {
padding: 0 20px;
}
`
export default DashboardLayout

View File

@ -0,0 +1 @@
export { default as DashboardLayout } from './Dashboard.layout'

View File

@ -35,19 +35,32 @@ export default function App({ Component, pageProps }: AppLayoutProps) {
<Global
styles={css`
:root {
--lsd-text-secondary: 255, 255, 255;
--grey-900: #141414;
--grey-800: #1f1f1f;
--grey-700: #2b2b2b;
--grey-600: #373737;
--grey-500: #434343;
--grey-400: #4f4f4f;
--grey-300: #5b5b5b;
--grey-200: #676767;
--grey-100: #737373;
}
html,
body {
background: white;
color: black;
background: rgb(var(--lsd-surface-primary));
margin: 0;
width: 100%;
height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: serif;
font-family: 'Courier';
}
* {
box-sizing: border-box;
}
#__next {

View File

@ -23,8 +23,8 @@ export default async function handler(request: NextRequest) {
width: '100%',
height: '100%',
justifyContent: 'flex-start',
backgroundColor: '#fff',
color: '#1400ff',
backgroundColor: '#000',
color: '#fff',
position: 'relative',
fontFamily: 'serif',
padding: '39px 45px',
@ -75,19 +75,7 @@ export default async function handler(request: NextRequest) {
margin: '0 auto',
}}
>
<div>Acid is designing the future.</div>
<div>
The future is a second enlightenment of the digital world.
</div>
<img
src="https://new-acid-info.vercel.app/og/mark.svg"
width={'400px'}
height={'400px'}
alt="og-asset"
style={{
position: 'absolute',
}}
/>
<div>Logos Ordinals</div>
</div>
</div>
</div>

18
src/pages/dashboard.tsx Normal file
View File

@ -0,0 +1,18 @@
import { SEO } from '@/components/SEO'
import { DashboardContainer } from '@/containers/Dashboard'
import { DashboardLayout } from '@/layouts/DashboardLayout'
type PageProps = {}
export default function DashboardPage(props: PageProps) {
return (
<>
<SEO />
<DashboardContainer />
</>
)
}
DashboardPage.getLayout = function getLayout(page: React.ReactNode) {
return <DashboardLayout>{page}</DashboardLayout>
}

View File

@ -4,12 +4,12 @@ import { localstored } from '@hookstate/localstored'
export type ThemeState = {
mode: 'light' | 'dark'
genericFontFamily: TypographyGenericFontFamily
genericFontFamily: TypographyGenericFontFamily | 'Courier'
}
export const defaultThemeState: ThemeState = {
mode: 'light',
genericFontFamily: 'serif',
genericFontFamily: 'Courier',
}
const themeState =
@ -23,7 +23,7 @@ const wrapThemeState = (state: State<ThemeState>) => ({
get: () => state.value,
setMode: (value: ThemeState['mode']) => state.mode.set(value),
setGenericFontFamily: (value: ThemeState['genericFontFamily']) =>
state.genericFontFamily.set('serif'),
state.genericFontFamily.set('Courier'),
toggleMode: () =>
state.mode.set(state.mode.get() === 'dark' ? 'light' : 'dark'),
})