Other user profile page

This commit is contained in:
LittleSheep 2025-01-02 22:40:56 +08:00
parent 6a4449d93c
commit c42e15cff0
7 changed files with 166 additions and 10 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -1,8 +1,11 @@
import type { NextConfig } from "next"; import type { NextConfig } from 'next'
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ /* config options here */
reactStrictMode: true, reactStrictMode: true,
}; images: {
domains: ['raw.sn.solsynth.dev', 'api.sn.solsynth.dev'],
},
}
export default nextConfig; export default nextConfig

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.3.0", "@mui/icons-material": "^6.3.0",
"@mui/material": "^6.3.0", "@mui/material": "^6.3.0",
"@mui/x-charts": "^7.23.2",
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-case-converter": "^1.1.1", "axios-case-converter": "^1.1.1",
"cookies-next": "^5.0.2", "cookies-next": "^5.0.2",

114
src/pages/users/[name].tsx Normal file
View File

@ -0,0 +1,114 @@
import { SnCheckInRecord } from '@/services/checkIn'
import { getAttachmentUrl, sni } from '@/services/network'
import { SnAccount } from '@/services/user'
import { Avatar, Box, Card, CardContent, Container, Grid2 as Grid, Typography } from '@mui/material'
import { LineChart } from '@mui/x-charts'
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
import Image from 'next/image'
export const getServerSideProps = (async (context) => {
const name = context.params!.name as string
try {
const { data: user } = await sni.get<SnAccount>('/cgi/id/users/' + name)
const { data: checkIn } = await sni.get<{ data: SnCheckInRecord[] }>('/cgi/id/users/' + name + '/check-in', {
params: { take: 14 },
})
return { props: { user, checkIn: checkIn.data } }
} catch (err) {
return {
notFound: true,
}
}
}) satisfies GetServerSideProps<{ user: SnAccount; checkIn: SnCheckInRecord[] }>
export default function UserProfile({ user, checkIn }: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
<>
{user.banner && (
<Box sx={{ aspectRatio: 16 / 5, position: 'relative' }}>
<Image src={getAttachmentUrl(user.banner)} alt="account banner" style={{ objectFit: 'cover' }} fill />
</Box>
)}
<Container sx={{ mt: 4, px: 2 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', gap: 2 }}>
{user && <Avatar src={getAttachmentUrl(user.avatar)} />}
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography fontWeight="bold">{user.nick}</Typography>
<Typography fontFamily="monospace" fontSize={13} lineHeight={1.2}>
@{user.name}
</Typography>
</Box>
</Box>
</Box>
<Grid container spacing={2} sx={{ mt: 3 }}>
<Grid size={8}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Fortune History
</Typography>
<LineChart
yAxis={[
{
data: [1, 2, 3, 4, 5],
tickMinStep: 1,
tickMaxStep: 1,
valueFormatter(value, _) {
const resultTierList = ['大凶', '凶', '中平', '吉', '大吉']
return resultTierList[value]
},
},
]}
xAxis={[
{
scaleType: 'time',
data: checkIn.map((c) => {
const og = new Date(c.createdAt)
og.setHours(0, 0, 0, 0)
return og
}),
valueFormatter(value, _) {
return new Date(value).toLocaleDateString('en-US', {
month: '2-digit',
day: '2-digit',
})
},
},
]}
series={[
{
data: checkIn.map((c) => c.resultTier),
valueFormatter(value, _) {
const resultTierList = ['大凶', '凶', '中平', '吉', '大吉']
return resultTierList[value ?? 0]
},
},
]}
height={300}
margin={{ top: 16, bottom: 24 }}
/>
</CardContent>
</Card>
</Grid>
<Grid size={4}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Information
</Typography>
<Typography variant="body2">
Born on {new Date(user.profile!.birthday!).toLocaleDateString()}
</Typography>
<Typography variant="body2">Joined at {new Date(user.createdAt).toLocaleDateString()}</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Container>
</>
)
}

View File

@ -1,12 +1,40 @@
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth' import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth'
import { Container } from '@mui/material' import { useUserStore } from '@/services/user'
import { useRouter } from 'next/router' import { Avatar, Box, Container, Typography } from '@mui/material'
import { getAttachmentUrl } from '@/services/network'
import { useEffect } from 'react' import { useEffect } from 'react'
import Image from 'next/image'
export default function UserItself() { export default function UserItself() {
useEffect(() => { useEffect(() => {
if (!checkAuthenticatedClient()) redirectToLogin() if (!checkAuthenticatedClient()) redirectToLogin()
}, []) }, [])
return <Container></Container> const userStore = useUserStore()
return (
<>
{userStore.account && (
<Box sx={{ aspectRatio: 16 / 5, position: 'relative' }}>
<Image src={getAttachmentUrl(userStore.account!.banner)} alt="account banner" objectFit="cover" fill />
</Box>
)}
<Container sx={{ mt: 5 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', gap: 2 }}>
{userStore.account && <Avatar src={getAttachmentUrl(userStore.account!.avatar)} />}
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<Typography fontWeight="bold">{userStore.account?.nick}</Typography>
<Typography fontFamily="monospace" fontSize={13}>
@{userStore.account?.name}
</Typography>
</Box>
</Box>
</Box>
<Box sx={{ mt: 5 }}></Box>
</Container>
</>
)
} }

10
src/services/checkIn.ts Normal file
View File

@ -0,0 +1,10 @@
export interface SnCheckInRecord {
id: number
createdAt: Date
updatedAt: Date
deletedAt?: Date | null
resultTier: number
resultExperience: number
resultModifiers: number[]
accountId: number
}

View File

@ -2,7 +2,7 @@ import { create } from 'zustand'
import { sni } from './network' import { sni } from './network'
import { hasCookie } from 'cookies-next/client' import { hasCookie } from 'cookies-next/client'
interface SnAccount { export interface SnAccount {
id: number id: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date
@ -24,7 +24,7 @@ interface SnAccount {
automatedId?: number | null automatedId?: number | null
} }
interface SnAccountContact { export interface SnAccountContact {
accountId: number accountId: number
content: string content: string
createdAt: Date createdAt: Date
@ -37,7 +37,7 @@ interface SnAccountContact {
verifiedAt?: Date | null verifiedAt?: Date | null
} }
interface SnAccountProfile { export interface SnAccountProfile {
id: number id: number
accountId: number accountId: number
birthday?: Date | null birthday?: Date | null
@ -50,7 +50,7 @@ interface SnAccountProfile {
updatedAt: Date updatedAt: Date
} }
interface SnAccountBadge { export interface SnAccountBadge {
id: number id: number
createdAt: Date createdAt: Date
updatedAt: Date updatedAt: Date