feat: impplement operator info page

This commit is contained in:
jinhojang6 2024-09-13 18:04:02 +09:00
parent b896eca534
commit 53d16ee34d
No known key found for this signature in database
GPG Key ID: 1762F21FE8B543F8
17 changed files with 514 additions and 37 deletions

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="M8.16663 10.5L4.66663 7L8.16663 3.5L8.98329 4.31667L6.29996 7L8.98329 9.68333L8.16663 10.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@ -1,5 +1,6 @@
<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 width="14" height="14" viewBox="0 0 14 14" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M9.10489 7.50376L11.6286 5.87535C11.7269 5.81573 11.8106 5.7349 11.8736 5.63878C11.9367 5.54265 11.9775 5.43365 11.993 5.31974C12.0086 5.20583 11.9984 5.08989 11.9634 4.98039C11.9284 4.87089 11.8694 4.77059 11.7906 4.68683L9.31327 2.20948C9.22951 2.13073 9.12922 2.07169 9.01971 2.03667C8.91021 2.00166 8.79427 1.99154 8.68036 2.00708C8.56645 2.02261 8.45745 2.0634 8.36133 2.12645C8.2652 2.18951 8.18437 2.27325 8.12476 2.37155L6.45775 4.86434L2.99254 5.6361C2.9133 5.65533 2.84053 5.69514 2.78162 5.75151C2.7227 5.80788 2.67971 5.87881 2.657 5.95713C2.63428 6.03544 2.63266 6.11837 2.65227 6.19751C2.67189 6.27666 2.71207 6.34922 2.76873 6.40786L7.51506 11.1619C7.57514 11.2163 7.64824 11.2542 7.72728 11.2721C7.80632 11.2899 7.88862 11.2871 7.96623 11.2638C8.04384 11.2405 8.11413 11.1976 8.1703 11.1392C8.22646 11.0808 8.2666 11.0089 8.28683 10.9304L9.10489 7.50376Z" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.13795 8.83887L1.98145 12.0185" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,5 +1,6 @@
import { breakpoints } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import Link from 'next/link'
import React from 'react'
interface Operator {
@ -15,57 +16,57 @@ interface OperatorGridProps {}
const operators: Operator[] = [
{
id: '1234',
id: '1',
image: '/dashboard/mock/operators/1.gif',
name: 'OP1234',
name: 'OP 1',
pointsPerHour: 304,
isStaked: false,
isPinned: false,
},
{
id: '1235',
id: '2',
image: '/dashboard/mock/operators/2.gif',
name: 'OP1235',
name: 'OP 2',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1236',
id: '3',
image: '/dashboard/mock/operators/3.gif',
name: 'OP1236',
name: 'OP 3',
pointsPerHour: 304,
isStaked: true,
isPinned: true,
},
{
id: '1237',
id: '4',
image: '/dashboard/mock/operators/4.gif',
name: 'OP1237',
name: 'OP 4',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1238',
id: '5',
image: '/dashboard/mock/operators/5.gif',
name: 'OP1238',
name: 'OP 5',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1239',
id: '6',
image: '/dashboard/mock/operators/6.gif',
name: 'OP1239',
name: 'OP 6',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
},
{
id: '1240',
id: '7',
image: '/dashboard/mock/operators/7.gif',
name: 'OP1240',
name: 'OP 7',
pointsPerHour: 304,
isStaked: true,
isPinned: false,
@ -108,7 +109,9 @@ const OperatorGrid: React.FC<OperatorGridProps> = () => {
<Grid>
{operators.map((operator) => (
<OperatorCard key={operator.id}>
<Link href={`/operators/${operator.id}`} key={operator.id}>
<OperatorImage src={operator.image} alt={operator.name} />
</Link>
<OperatorInfo>
<OperatorName>{operator.name}</OperatorName>
<PointsPerHour>
@ -190,7 +193,8 @@ const IconButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgb(var(--lsd-border-primary));
border: none;
border-left: 1px solid rgb(var(--lsd-border-primary));
cursor: pointer;
height: 28px;
`
@ -230,6 +234,12 @@ const Grid = styled.div`
const OperatorCard = styled.div`
display: flex;
flex-direction: column;
color: rgb(var(--lsd-text-primary));
a {
display: flex;
}
`
const OperatorImage = styled.img`
@ -251,13 +261,14 @@ const OperatorName = styled.div`
const PointsPerHour = styled.div`
display: flex;
justify-content: space-between;
margin-top: 8px;
align-items: center;
`
const Actions = styled.div`
display: flex;
padding: 16px 0;
align-items: center;
border: 1px solid rgb(var(--lsd-border-primary));
border-radius: 0;
`
const ActionButton = styled.button<{ isStaked: boolean }>`
@ -268,10 +279,7 @@ const ActionButton = styled.button<{ isStaked: boolean }>`
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;
border: none;
font-weight: 400;
font-size: 12px;
line-height: 16px;

View File

@ -62,7 +62,7 @@ const ProgressBar: React.FC<ProgressBarProps> = ({
<ProgressFooter>
<TimeRemaining>
<Label>Time Remaining</Label>
<Value color="#fe740c" backgroundColor="#321504">
<Value color="var(--orange)" backgroundColor="var(--dark-orange)">
{timeRemaining}
</Value>
</TimeRemaining>
@ -162,7 +162,7 @@ const ProgressFooter = styled.div`
`
const TimeRemaining = styled.div`
border: 1px solid #321504;
border: 1px solid var(--dark-orange);
display: flex;
align-items: center;
gap: 16px;

View File

@ -57,7 +57,10 @@ const OperatorGrid: React.FC<OperatorGridProps> = () => {
return (
<StyledOperatorGrid>
{operatorImages.map((operator, index) => (
<Link href={`/explore/${index + 1}`} key={'explore-operator-' + index}>
<Link
href={`/operators/${index + 1}`}
key={'explore-operator-' + index}
>
<GridItem>
<img
key={index}

View File

@ -0,0 +1,243 @@
import { breakpoints } from '@/configs/ui.configs'
import styled from '@emotion/styled'
import React from 'react'
interface OperatorDetailsProps {
id: number
}
const operatorInfo = [
{ trait: 'Inscription ID', value: '84783..ai0' },
{ trait: 'Inscription Number', value: '123987' },
{ trait: 'Owner', value: 'bc1qa...vehs9' },
{ trait: 'Content', value: 'Link' },
{ trait: 'Content Type', value: 'image/png' },
{ trait: 'Created', value: '2/14/2023, 11:01:50 PM' },
{ trait: 'Genesis Transaction', value: '88244...8da' },
{ trait: 'Genesis Transaction BlockHeight', value: '776604' },
{ trait: 'Sat Rarity', value: 'COMMON' },
{ trait: 'Sat Number', value: '1737591324951162' },
{ trait: 'Sat Name', value: 'bnskqioxomx' },
{ trait: 'Sat BlockTime', value: '11/14/2018, 9:52:23 AM' },
{ trait: 'Sat BlockHeight', value: '550073' },
{ trait: 'Location', value: '7133b...1:0' },
{ trait: 'Location BlockHeight', value: 'bc1qa...vehs9' },
{ trait: 'Output', value: '7133b...b:1' },
]
const OperatorDetails: React.FC<OperatorDetailsProps> = ({
id,
}: OperatorDetailsProps) => {
const handleDownload = () => {
window.open(`/dashboard/mock/operators/${id % 7}.gif`, '_blank')
}
const handleShare = async () => {
if (navigator.share) {
try {
await navigator.share({
title: 'Logos Operator',
text: 'Check out this Logos Operator!',
url: window.location.href,
})
} catch (error) {
console.error('Error sharing:', error)
}
} else {
alert('Web Share API is not supported in this browser.')
}
}
return (
<Container>
<OperatorImage>
<img
src={`/dashboard/mock/operators/${id % 7}.gif`}
alt={`Operator ${id}`}
className="operator-img"
/>
<ActionButtons>
<Button onClick={handleShare}>Share</Button>
<Button onClick={handleDownload}>Download</Button>
</ActionButtons>
</OperatorImage>
<OperatorInfo>
<OperatorTitle>Memetic</OperatorTitle>
<OperatorSubtitle>
<span>Operator</span>
<span className="id">#{id}</span>
</OperatorSubtitle>
<AttributesFirstGrid>
<AttributeItem>
<AttributeLabel>Count</AttributeLabel>
<AttributeValue>3</AttributeValue>
</AttributeItem>
<AttributeItem>
<AttributeLabel>Body</AttributeLabel>
<AttributeValue>Purple</AttributeValue>
</AttributeItem>
</AttributesFirstGrid>
<AttributesSecondGrid>
<AttributeItem>
<AttributeLabel>Head</AttributeLabel>
<AttributeValue>Woodsman</AttributeValue>
</AttributeItem>
<AttributeItem>
<AttributeLabel>Eyes</AttributeLabel>
<AttributeValue>Patch</AttributeValue>
</AttributeItem>
<AttributeItem>
<AttributeLabel>Earing</AttributeLabel>
<AttributeValue>None</AttributeValue>
</AttributeItem>
</AttributesSecondGrid>
<ArchetypeSection>
<span>Archetype</span>
<span>Memetic</span>
</ArchetypeSection>
<DetailsList>
{operatorInfo.map((info, index) => (
<DetailItem key={index}>
<DetailLabel>{info.trait}</DetailLabel>
<DetailValue>{info.value}</DetailValue>
</DetailItem>
))}
</DetailsList>
</OperatorInfo>
</Container>
)
}
const Container = styled.section`
display: grid;
grid-template-columns: repeat(24, 1fr);
gap: 0 16px;
width: 100%;
@media (max-width: ${breakpoints.md}px) {
grid-template-columns: repeat(1, 1fr);
margin-top: 40px;
}
`
const OperatorImage = styled.div`
position: relative;
img {
width: 100%;
height: auto;
}
grid-column: 1 / 13;
@media (max-width: ${breakpoints.md}px) {
grid-column: 1 / 2;
}
`
const OperatorInfo = styled.div`
grid-column: 15 / 25;
@media (max-width: ${breakpoints.md}px) {
grid-column: 1 / 2;
}
`
const ActionButtons = styled.div`
display: flex;
gap: -1px;
margin-top: 16px;
button:first-of-type {
border-right: none;
}
`
const Button = styled.button`
flex: 1;
padding: 10px 40px;
border: 1px solid #fff;
background: transparent;
color: #fff;
font: 400 14px/1 Courier, sans-serif;
cursor: pointer;
`
const OperatorTitle = styled.h1`
font-size: 32px;
font-weight: 400;
line-height: 40px;
margin: 0;
`
const OperatorSubtitle = styled.div`
display: flex;
gap: 16px;
font-size: 18px;
margin-top: 16px;
.id {
opacity: 0.5;
}
`
const AttributesFirstGrid = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2px;
margin-top: 24px;
`
const AttributesSecondGrid = styled(AttributesFirstGrid)`
grid-template-columns: repeat(3, 1fr);
margin-top: 2px;
`
const AttributeItem = styled.div`
background-color: var(--dark-orange);
padding: 16px 8px;
`
const AttributeLabel = styled.div`
font-size: 14px;
color: var(--orange);
`
const AttributeValue = styled.div`
margin-top: 8px;
font-size: 14px;
color: var(--orange);
`
const ArchetypeSection = styled.div`
display: flex;
justify-content: space-between;
background-color: var(--grey-900);
padding: 16px 8px;
margin-top: 24px;
`
const DetailsList = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 24px;
`
const DetailItem = styled.div`
display: flex;
justify-content: space-between;
background-color: var(--grey-900);
padding: 16px 8px;
`
const DetailLabel = styled.span`
font-size: 14px;
`
const DetailValue = styled.span`
font-size: 14px;
`
export default OperatorDetails

View File

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

View File

@ -0,0 +1,81 @@
import styled from '@emotion/styled'
import Link from 'next/link'
import React from 'react'
interface SimilarOperatorsProps {}
const operators = [
{
id: 1,
src: '/dashboard/mock/operators/1.gif',
},
{
id: 2,
src: '/dashboard/mock/operators/2.gif',
},
{
id: 3,
src: '/dashboard/mock/operators/3.gif',
},
{
id: 4,
src: '/dashboard/mock/operators/4.gif',
},
{
id: 5,
src: '/dashboard/mock/operators/5.gif',
},
{
id: 6,
src: '/dashboard/mock/operators/6.gif',
},
]
const SimilarOperators: React.FC<SimilarOperatorsProps> = () => {
return (
<Container>
<SectionTitle>Similar Operators</SectionTitle>
<OperatorGrid>
{operators.map((operator) => (
<Link href={`/operators/${operator.id}`} key={operator.id}>
<OperatorImage
key={operator.id}
src={operator.src}
alt={`Similar Operator ${operator.id}`}
/>
</Link>
))}
</OperatorGrid>
</Container>
)
}
const Container = styled.section`
margin-top: 72px;
@media (max-width: 991px) {
margin-top: 40px;
}
`
const SectionTitle = styled.h2`
font-size: 28px;
margin: 0 0 24px 0;
`
const OperatorGrid = styled.div`
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
`
// full width, aspect ratio 1 in grid
const OperatorImage = styled.img`
width: 100%;
aspect-ratio: 1;
object-fit: cover;
cursor: pointer;
`
export default SimilarOperators

View File

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

View File

@ -26,14 +26,6 @@ const StyledExploreSection = styled.main`
line-height: 48px;
margin-top: 60px;
}
@media (max-width: 991px) {
padding: 0 20px;
.section-title {
margin-top: 40px;
}
}
`
export default ExploreSection

View File

@ -0,0 +1,47 @@
import { OperatorDetails } from '@/components/Operator/OperatorDetails'
import { SimilarOperators } from '@/components/Operator/SimilarOperators'
import styled from '@emotion/styled'
import { useRouter } from 'next/router'
import React from 'react'
interface ExploreOperatorProps {
id: string
}
const ExploreOperator: React.FC<ExploreOperatorProps> = ({ id }) => {
const router = useRouter()
const handleGoBack = () => {
router.back()
}
return (
<Container>
<GoBackButton aria-label="Share" onClick={handleGoBack}>
<img src={`/assets/chevron-left.svg`} alt="Share icon" />
</GoBackButton>
<OperatorDetails id={Number(id)} />
<SimilarOperators />
</Container>
)
}
const Container = styled.div`
display: flex;
flex-direction: column;
overflow: hidden;
`
const GoBackButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
margin-top: 36px;
width: 28px;
height: 28px;
border: 1px solid rgba(var(--lsd-border-primary));
background: transparent;
margin-bottom: 16px;
cursor: pointer;
`
export default ExploreOperator

View File

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

View File

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

View File

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

View File

@ -44,6 +44,9 @@ export default function App({ Component, pageProps }: AppLayoutProps) {
--grey-300: #5b5b5b;
--grey-200: #676767;
--grey-100: #737373;
--orange: #fe740c;
--dark-orange: #321504;
}
html,

View File

@ -0,0 +1,57 @@
import { SEO } from '@/components/SEO'
import { OperatorContainer } from '@/containers/Operator'
import { OperatorInfoLayout } from '@/layouts/OperatorInfoLayout'
const Page = ({ id }: any) => {
return (
<>
<SEO pagePath={`/operators/${id}`} />
<OperatorContainer id={id} />
</>
)
}
Page.getLayout = function getLayout(page: React.ReactNode) {
return <OperatorInfoLayout>{page}</OperatorInfoLayout>
}
export async function getStaticPaths() {
const paths: any = []
// operator id from 1 to 1000
const operators = Array.from({ length: 1000 }, (_, i) => i + 1)
operators.forEach((operator) => {
paths.push({
params: {
id: operator.toString(),
},
})
})
return {
paths,
fallback: false,
}
}
export async function getStaticProps({ params }: any) {
const { id } = params
try {
return {
props: {
id,
},
}
} catch (error) {
return {
props: {
jobs: [],
issues: {},
},
}
}
}
export default Page