Compare commits

..

No commits in common. "5d6a018c63e6d832271c71eab0400f0ded5ebd6c" and "945bd5d3578572650461901fd9db4ee744ba9b56" have entirely different histories.

38 changed files with 222 additions and 511 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -9,7 +9,6 @@
"lint": "next lint"
},
"dependencies": {
"solar-js-sdk": "./packages/sn",
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0",

175
packages/sn/.gitignore vendored
View File

@ -1,175 +0,0 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

Binary file not shown.

View File

@ -1,31 +0,0 @@
{
"name": "solar-js-sdk",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
"tsup": {
"entry": [
"src/index.ts"
],
"splitting": true,
"sourcemap": true,
"clean": true,
"dts": true,
"format": "esm"
},
"scripts": {
"build": "tsup"
},
"devDependencies": {
"@types/bun": "latest",
"tsup": "^8.3.5"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"axios": "^1.7.9",
"universal-cookie": "^7.2.2",
"zustand": "^5.0.3"
}
}

View File

@ -1,7 +0,0 @@
export * from './matrix/product'
export * from './attachment'
export * from './auth'
export * from './checkIn'
export * from './network'
export * from './post'
export * from './user'

View File

@ -1,113 +0,0 @@
import axios, { type AxiosInstance } from 'axios'
import Cookies from 'universal-cookie'
import { setTokenCookies } from './auth'
function toCamelCase(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(toCamelCase)
} else if (obj && typeof obj === 'object') {
return Object.keys(obj).reduce((result: any, key) => {
const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase())
result[camelKey] = toCamelCase(obj[key])
return result
}, {})
}
return obj
}
// function toSnakeCase(obj: any): any {
// if (Array.isArray(obj)) {
// return obj.map(toSnakeCase)
// } else if (obj && typeof obj === 'object') {
// return Object.keys(obj).reduce((result: any, key) => {
// const snakeKey = key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`)
// result[snakeKey] = toSnakeCase(obj[key])
// return result
// }, {})
// }
// return obj
// }
const baseURL = 'https://api.sn.solsynth.dev'
export const sni: AxiosInstance = (() => {
const inst = axios.create({
baseURL,
})
inst.interceptors.request.use(
async (config) => {
const tk = await refreshToken()
if (tk) config.headers['Authorization'] = `Bearer ${tk}`
return config
},
(error) => error,
)
inst.interceptors.response.use(
(response) => {
if (response.data) {
response.data = toCamelCase(response.data)
}
return response
},
(error) => {
if (error.response && error.response.data) {
error.response.data = toCamelCase(error.response.data)
}
return Promise.reject(error)
},
)
return inst
})()
async function refreshToken(): Promise<string | undefined> {
const cookies = new Cookies()
if (!cookies.get('nex_user_atk') || !cookies.get('nex_user_rtk')) return
const ogTk: string = cookies.get('nex_user_atk')!
if (!isTokenExpired(ogTk)) return ogTk
const resp = await axios.post(
'/cgi/id/auth/token',
{
refresh_token: cookies.get('nex_user_rtk')!,
grant_type: 'refresh_token',
},
{ baseURL },
)
const atk: string = resp.data['access_token']
const rtk: string = resp.data['refresh_token']
setTokenCookies(atk, rtk)
console.log('[Authenticator] Refreshed token...')
return atk
}
function isTokenExpired(token: string): boolean {
try {
const parts = token.split('.')
if (parts.length !== 3) {
throw new Error('Invalid JWT format')
}
const payload = JSON.parse(atob(parts[1]))
if (!payload.exp) {
throw new Error("'exp' claim is missing in the JWT payload")
}
const now = Math.floor(Date.now() / 1000)
return now >= payload.exp
} catch (error) {
console.error('[Authenticator] Something went wrong with token: ', error)
return true
}
}
export function getAttachmentUrl(identifer: string): string {
if (identifer.startsWith('http')) return identifer
return `${baseURL}/cgi/uc/attachments/${identifer}`
}

View File

@ -1,83 +0,0 @@
import { create } from 'zustand'
import { sni } from './network'
import Cookies from 'universal-cookie'
export interface SnAccount {
id: number
createdAt: Date
updatedAt: Date
deletedAt?: Date | null
confirmedAt?: Date | null
contacts?: SnAccountContact[] | null
avatar: string
banner: string
description: string
name: string
nick: string
permNodes: Record<string, any>
profile?: SnAccountProfile | null
badges: SnAccountBadge[]
suspendedAt?: Date | null
affiliatedId?: number | null
affiliatedTo?: number | null
automatedBy?: number | null
automatedId?: number | null
}
export interface SnAccountContact {
accountId: number
content: string
createdAt: Date
deletedAt?: Date | null
id: number
isPrimary: boolean
isPublic: boolean
type: number
updatedAt: Date
verifiedAt?: Date | null
}
export interface SnAccountProfile {
id: number
accountId: number
birthday?: Date | null
createdAt: Date
deletedAt?: Date | null
experience: number
firstName: string
lastName: string
lastSeenAt?: Date | null
updatedAt: Date
}
export interface SnAccountBadge {
id: number
createdAt: Date
updatedAt: Date
deletedAt?: Date | null
type: string
accountId: number
metadata: Record<string, any>
}
export interface UserStore {
account: SnAccount | undefined
fetchUser: () => Promise<SnAccount | undefined>
}
export const useUserStore = create<UserStore>((set) => ({
account: undefined,
fetchUser: async (): Promise<SnAccount | undefined> => {
const cookies = new Cookies()
if (!cookies.get('nex_user_atk')) return
try {
const resp = await sni.get<SnAccount>('/cgi/id/users/me')
set({ account: resp.data })
console.log('[Authenticator] Logged in as @' + resp.data.name)
return resp.data
} catch (err) {
console.error('[Authenticator] Unable to get user profile: ', err)
return
}
},
}))

View File

@ -1,27 +0,0 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

View File

@ -1,6 +1,6 @@
import { useUserStore } from 'solar-js-sdk'
import { useUserStore } from '@/services/user'
import { AppBar, AppBarProps, Avatar, IconButton, Toolbar, Typography, useScrollTrigger, useTheme } from '@mui/material'
import { getAttachmentUrl } from 'solar-js-sdk'
import { getAttachmentUrl } from '@/services/network'
import MenuIcon from '@mui/icons-material/Menu'
import AccountCircle from '@mui/icons-material/AccountCircle'
import Link from 'next/link'

View File

@ -1,5 +1,5 @@
import { SnAttachment } from 'solar-js-sdk'
import { getAttachmentUrl } from 'solar-js-sdk'
import { SnAttachment } from '@/services/attachment'
import { getAttachmentUrl } from '@/services/network'
import { QuestionMark } from '@mui/icons-material'
import { Link, Paper, Typography } from '@mui/material'
import { ComponentProps } from 'react'

View File

@ -1,13 +1,14 @@
'use client'
import { setTokenCookies, SnAuthFactor, SnAuthResult, SnAuthTicket } from 'solar-js-sdk'
import { sni } from 'solar-js-sdk'
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth'
import { sni } from '@/services/network'
import { ArrowForward } from '@mui/icons-material'
import { Collapse, Alert, Box, TextField, Button } from '@mui/material'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import ErrorIcon from '@mui/icons-material/Error'
import { setCookie } from 'cookies-next/client'
export interface SnLoginCheckpointForm {
password: string
@ -43,7 +44,8 @@ export function SnLoginCheckpoint({
})
const atk: string = tokenResp.data['accessToken']
const rtk: string = tokenResp.data['refreshToken']
setTokenCookies(atk, rtk)
setCookie('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
setCookie('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
console.log('[Authenticator] User has been logged in. Result atk: ', atk)
}

View File

@ -1,7 +1,7 @@
'use client'
import { SnAuthFactor, SnAuthTicket } from 'solar-js-sdk'
import { sni } from 'solar-js-sdk'
import { SnAuthFactor, SnAuthTicket } from '@/services/auth'
import { sni } from '@/services/network'
import { Collapse, Alert, Box, Button, Typography, ButtonGroup } from '@mui/material'
import { useState } from 'react'

View File

@ -1,10 +1,10 @@
'use client'
import { useState } from 'react'
import { sni } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { ArrowForward } from '@mui/icons-material'
import { Alert, Box, Button, Collapse, Link, TextField, Typography } from '@mui/material'
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from 'solar-js-sdk'
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth'
import { useForm } from 'react-hook-form'
import NextLink from 'next/link'

View File

@ -1,4 +1,4 @@
import { checkAuthenticatedClient, redirectToLogin } from 'solar-js-sdk'
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth'
import { JSX, useEffect } from 'react'
import { DashboardLayout, Navigation } from '@toolpad/core'
import { Box, Stack, Typography } from '@mui/material'

View File

@ -5,7 +5,7 @@ import { Roboto } from 'next/font/google'
import { CapAppBar } from '@/components/CapAppBar'
import { PagesProgressBar as ProgressBar } from 'next-nprogress-bar'
import { AppProvider } from '@toolpad/core/nextjs'
import { useUserStore } from 'solar-js-sdk'
import { useUserStore } from '@/services/user'
import { useEffect } from 'react'
import Head from 'next/head'

View File

@ -1,6 +1,6 @@
import { AttachmentItem } from '@/components/attachments/AttachmentItem'
import { SnAttachment } from 'solar-js-sdk'
import { sni } from 'solar-js-sdk'
import { SnAttachment } from '@/services/attachment'
import { sni } from '@/services/network'
import { Box, ImageList, ImageListItem, Pagination, useMediaQuery, useTheme } from '@mui/material'
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { useRouter } from 'next/router'

View File

@ -1,8 +1,8 @@
import { sni } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { Container, Box, Typography, Alert, Collapse, Button, CircularProgress, Card, CardContent } from '@mui/material'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { checkAuthenticatedClient, redirectToLogin, SnAuthTicket } from 'solar-js-sdk'
import { checkAuthenticatedClient, redirectToLogin, SnAuthTicket } from '@/services/auth'
import ErrorIcon from '@mui/icons-material/Error'
import CloseIcon from '@mui/icons-material/Close'

View File

@ -1,8 +1,8 @@
import { SnLoginCheckpoint } from '@/components/auth/SnLoginCheckpoint'
import { SnLoginRouter } from '@/components/auth/SnLoginRouter'
import { SnLoginStart } from '@/components/auth/SnLoginStart'
import { SnAuthFactor, SnAuthTicket } from 'solar-js-sdk'
import { useUserStore } from 'solar-js-sdk'
import { SnAuthFactor, SnAuthTicket } from '@/services/auth'
import { useUserStore } from '@/services/user'
import { Box, Container, Typography } from '@mui/material'
import { useRouter } from 'next/router'
import { useState } from 'react'

View File

@ -26,7 +26,7 @@ export default function ConsoleLanding() {
<Typography variant="subtitle1">of the Solar Network</Typography>
</Box>
<Grid container columns={{ xs: 1, sm: 2, md: 3 }} spacing={4}>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}>
<Grid size={1}>
<NextLink passHref href="/console/matrix">
<CardActionArea>

View File

@ -1,7 +1,7 @@
import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout'
import { MaProduct } from 'solar-js-sdk'
import { sni } from 'solar-js-sdk'
import { Typography, Container, Box, Button, Grid2 as Grid, Card, CardContent, CardActions } from '@mui/material'
import { MaProduct } from '@/services/matrix/product'
import { sni } from '@/services/network'
import { Typography, Container, Box, Button, Grid2 as Grid, Card, CardActionArea, CardContent } from '@mui/material'
import NextLink from 'next/link'
import { useEffect, useState } from 'react'
@ -36,21 +36,21 @@ export default function MatrixMarketplace() {
Matrix Marketplace
</Typography>
<Grid container columns={{ xs: 1, sm: 2, md: 3 }} spacing={4}>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}>
{products.map((p) => (
<Grid size={1} key={p.id}>
<Card sx={{ width: '100%' }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{p.name}
</Typography>
<Typography variant="body1">{p.description}</Typography>
</CardContent>
<CardActions>
<Button size="small">Details</Button>
<Button size="small">Edit</Button>
</CardActions>
</Card>
<NextLink passHref href="/console/matrix">
<CardActionArea>
<Card sx={{ width: '100%' }}>
<CardContent>
<Typography variant="h5" gutterBottom>
{p.name}
</Typography>
<Typography variant="body1">{p.description}</Typography>
</CardContent>
</Card>
</CardActionArea>
</NextLink>
</Grid>
))}
</Grid>

View File

@ -3,8 +3,8 @@ import { Typography, Container, Box, Button, TextField, Collapse, Alert } from '
import { useForm } from 'react-hook-form'
import { useState } from 'react'
import { useRouter } from 'next/router'
import { sni } from 'solar-js-sdk'
import NextLink from 'next/link'
import { sni } from '@/services/network'
import ErrorIcon from '@mui/icons-material/Error'

View File

@ -1,4 +1,4 @@
import { sni } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { Container, Box, Typography, CircularProgress, Alert, Collapse } from '@mui/material'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'

View File

@ -1,4 +1,4 @@
import { sni } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { Container, Box, Typography, Alert, Collapse, Button } from '@mui/material'
import { useRouter } from 'next/router'
import { useState } from 'react'

View File

@ -1,4 +1,4 @@
import { sni } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { Container, Box, Typography, Alert, Collapse, Button, TextField } from '@mui/material'
import { useRouter } from 'next/router'
import { useState } from 'react'

View File

@ -1,6 +1,6 @@
import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnPost } from 'solar-js-sdk'
import { listAttachment, SnAttachment } from 'solar-js-sdk'
import { getAttachmentUrl, sni } from '@/services/network'
import { SnPost } from '@/services/post'
import { listAttachment, SnAttachment } from '@/services/attachment'
import {
Grid2 as Grid,
Alert,

View File

@ -1,5 +1,5 @@
import { sni } from 'solar-js-sdk'
import { SnPost } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { SnPost } from '@/services/post'
import { GetServerSideProps } from 'next'
import { Feed } from 'feed'

View File

@ -1,7 +1,7 @@
import { AttachmentItem } from '@/components/attachments/AttachmentItem'
import { SnAttachment, listAttachment } from 'solar-js-sdk'
import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnPost } from 'solar-js-sdk'
import { SnAttachment, listAttachment } from '@/services/attachment'
import { getAttachmentUrl, sni } from '@/services/network'
import { SnPost } from '@/services/post'
import { Avatar, Box, Container, Divider, Grid2 as Grid, Link, Pagination, Paper, Typography } from '@mui/material'
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import NextLink from 'next/link'

View File

@ -1,5 +1,5 @@
import { sni } from 'solar-js-sdk'
import { SnPost } from 'solar-js-sdk'
import { sni } from '@/services/network'
import { SnPost } from '@/services/post'
import { GetServerSideProps } from 'next'
import { EnumChangefreq, SitemapItem, SitemapStream, streamToPromise } from 'sitemap'
import { Readable } from 'stream'

View File

@ -1,10 +1,9 @@
import { SnCheckInRecord } from 'solar-js-sdk'
import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnAccount } from 'solar-js-sdk'
import { SnCheckInRecord } from '@/services/checkIn'
import { getAttachmentUrl, sni } from '@/services/network'
import { SnAccount, SnAccountBadgeMapping } 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 { SnAccountBadgeMapping } from '@/services/user'
import Image from 'next/image'
export const getServerSideProps = (async (context) => {

View File

@ -1,14 +1,14 @@
import { checkAuthenticatedClient, redirectToLogin } from 'solar-js-sdk'
import { useUserStore } from 'solar-js-sdk'
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth'
import { useUserStore } from '@/services/user'
import { Avatar, Box, Button, Container, Typography } from '@mui/material'
import { getAttachmentUrl } from 'solar-js-sdk'
import { getAttachmentUrl } from '@/services/network'
import { useEffect } from 'react'
import { removeTokenCookies } from 'solar-js-sdk'
import Image from 'next/image'
import LogoutIcon from '@mui/icons-material/Logout'
import LaunchIcon from '@mui/icons-material/Launch'
import Link from 'next/link'
import { deleteCookie } from 'cookies-next/client'
export default function UserItself() {
useEffect(() => {
@ -18,7 +18,8 @@ export default function UserItself() {
const userStore = useUserStore()
function logout() {
removeTokenCookies()
deleteCookie('nex_user_atk')
deleteCookie('nex_user_rtk')
window.location.reload()
}

View File

@ -1,4 +1,4 @@
import Cookies from 'universal-cookie'
import { hasCookie } from 'cookies-next/client'
export interface SnAuthResult {
isFinished: boolean
@ -35,21 +35,8 @@ export interface SnAuthFactor {
accountId?: number | null
}
export function setTokenCookies(atk: string, rtk: string) {
const cookies = new Cookies()
cookies.set('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
cookies.set('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
}
export function removeTokenCookies() {
const cookies = new Cookies()
cookies.remove('nex_user_atk')
cookies.remove('nex_user_rtk')
}
export function checkAuthenticatedClient(): boolean {
const cookies = new Cookies()
return !!cookies.get('nex_user_atk')
return !!hasCookie('nex_user_atk')
}
export function redirectToLogin() {

77
src/services/network.ts Normal file
View File

@ -0,0 +1,77 @@
import axios, { AxiosInstance } from 'axios'
import applyCaseMiddleware from 'axios-case-converter'
import { hasCookie, getCookie, setCookie } from 'cookies-next/client'
const baseURL = 'https://api.sn.solsynth.dev'
export const sni: AxiosInstance = (() => {
const inst = axios.create({
baseURL,
})
inst.interceptors.request.use(
async (config) => {
const tk = await refreshToken()
if (tk) config.headers['Authorization'] = `Bearer ${tk}`
return config
},
(error) => error,
)
applyCaseMiddleware(inst, {
ignoreParams: true,
ignoreHeaders: true,
})
return inst
})()
async function refreshToken(): Promise<string | undefined> {
if (!hasCookie('nex_user_atk') || !hasCookie('nex_user_rtk')) return
const ogTk: string = getCookie('nex_user_atk')!
if (!isTokenExpired(ogTk)) return ogTk
const resp = await axios.post(
'/cgi/id/auth/token',
{
refresh_token: getCookie('nex_user_rtk')!,
grant_type: 'refresh_token',
},
{ baseURL },
)
const atk: string = resp.data['access_token']
const rtk: string = resp.data['refresh_token']
setCookie('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
setCookie('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
console.log('[Authenticator] Refreshed token...')
return atk
}
function isTokenExpired(token: string): boolean {
try {
const parts = token.split('.')
if (parts.length !== 3) {
throw new Error('Invalid JWT format')
}
const payload = JSON.parse(atob(parts[1]))
if (!payload.exp) {
throw new Error("'exp' claim is missing in the JWT payload")
}
const now = Math.floor(Date.now() / 1000)
return now >= payload.exp
} catch (error) {
console.error('[Authenticator] Something went wrong with token: ', error)
return true
}
}
export function getAttachmentUrl(identifer: string): string {
if (identifer.startsWith('http')) return identifer
return `${baseURL}/cgi/uc/attachments/${identifer}`
}

View File

@ -1,8 +1,69 @@
import { create } from 'zustand'
import { sni } from './network'
import { hasCookie } from 'cookies-next/client'
import { JSX } from 'react'
import ConstructionIcon from '@mui/icons-material/Construction'
import FlagIcon from '@mui/icons-material/Flag'
export interface SnAccount {
id: number
createdAt: Date
updatedAt: Date
deletedAt?: Date | null
confirmedAt?: Date | null
contacts?: SnAccountContact[] | null
avatar: string
banner: string
description: string
name: string
nick: string
permNodes: Record<string, any>
profile?: SnAccountProfile | null
badges: SnAccountBadge[]
suspendedAt?: Date | null
affiliatedId?: number | null
affiliatedTo?: number | null
automatedBy?: number | null
automatedId?: number | null
}
export interface SnAccountContact {
accountId: number
content: string
createdAt: Date
deletedAt?: Date | null
id: number
isPrimary: boolean
isPublic: boolean
type: number
updatedAt: Date
verifiedAt?: Date | null
}
export interface SnAccountProfile {
id: number
accountId: number
birthday?: Date | null
createdAt: Date
deletedAt?: Date | null
experience: number
firstName: string
lastName: string
lastSeenAt?: Date | null
updatedAt: Date
}
export interface SnAccountBadge {
id: number
createdAt: Date
updatedAt: Date
deletedAt?: Date | null
type: string
accountId: number
metadata: Record<string, any>
}
export const SnAccountBadgeMapping: Record<string, { icon: JSX.Element; name: string }> = {
'company.staff': {
icon: <ConstructionIcon />,
@ -13,3 +74,24 @@ export const SnAccountBadgeMapping: Record<string, { icon: JSX.Element; name: st
name: 'Solar Network Natives',
},
}
export interface UserStore {
account: SnAccount | undefined
fetchUser: () => Promise<SnAccount | undefined>
}
export const useUserStore = create<UserStore>((set) => ({
account: undefined,
fetchUser: async (): Promise<SnAccount | undefined> => {
if (!hasCookie('nex_user_atk')) return
try {
const resp = await sni.get<SnAccount>('/cgi/id/users/me')
set({ account: resp.data })
console.log('[Authenticator] Logged in as @' + resp.data.name)
return resp.data
} catch (err) {
console.error('[Authenticator] Unable to get user profile: ', err)
return
}
},
}))