Skip to content

Commit b4c3f11

Browse files
committed
feat(market): add market page
1 parent 76d7efd commit b4c3f11

File tree

7 files changed

+259
-5
lines changed

7 files changed

+259
-5
lines changed

src/app/(default)/m/[uid]/layout.tsx

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use client'
2+
3+
import React, {useEffect, useState} from "react";
4+
import {notifications} from "@mantine/notifications";
5+
import {AppWrite} from "@/server/Client";
6+
import {ProductList} from "@/server/types";
7+
import usePageContext from "@/store/usePageContext";
8+
import {ProductApi} from "@/server/ProductApi";
9+
10+
11+
export default function RepoLayout(props: { children: React.ReactNode, params: Promise<{ uid: string }> }) {
12+
const api = new ProductApi();
13+
const [Loading, setLoading] = useState(false);
14+
const [NotFound, setNotFound] = useState(false);
15+
const [Product,setProduct] = useState<ProductList | undefined>();
16+
const [Parma,setParma] = useState<{ uid: string } | undefined>();
17+
const context = usePageContext();
18+
19+
const Init = async () => {
20+
const {uid} = await props.params;
21+
setParma({uid});
22+
const basic = await api.Info(uid);
23+
if (basic.status !== 200){
24+
notifications
25+
.show({
26+
title: '数据请求失败',
27+
message: 'Repo Not Found',
28+
color: 'red',
29+
});
30+
}
31+
const json: AppWrite<ProductList> = JSON.parse(basic.data);
32+
if (json.code !== 200 || !json.data) {
33+
setNotFound(true);
34+
return;
35+
}
36+
setProduct(json.data);
37+
context.setProduct(json.data);
38+
setLoading(true);
39+
}
40+
useEffect(() => {
41+
Init().then().catch();
42+
}, []);
43+
44+
return (
45+
<div className="market">
46+
{
47+
NotFound && (
48+
<div className="not-found">
49+
<h1>404</h1>
50+
<span>Repo Not Found</span>
51+
</div>
52+
)
53+
}
54+
{
55+
(Product && Loading && Parma) && (
56+
<>
57+
{props.children}
58+
</>
59+
)
60+
}
61+
</div>
62+
);
63+
}

src/app/(default)/m/[uid]/page.tsx

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
'use client'
2+
3+
import usePageContext from "@/store/usePageContext";
4+
import {ProductList} from "@/server/types";
5+
import {useEffect, useState} from "react";
6+
import {Button} from "@mantine/core";
7+
import {RepoApi} from "@/server/RepoApi";
8+
import ReactMarkdown from "react-markdown";
9+
import rehypeRaw from "rehype-raw";
10+
import dayjs from "dayjs"
11+
import relativeTime from "dayjs/plugin/relativeTime"
12+
import {useRouter} from "next/navigation";
13+
dayjs.extend(relativeTime)
14+
export default function MarketPage() {
15+
const context = usePageContext();
16+
const [Product, setProduct] = useState<ProductList | undefined>();
17+
const [Readme, setReadme] = useState<string>("")
18+
const repo_api = new RepoApi();
19+
const nav = useRouter().replace;
20+
21+
useEffect(() => {
22+
if (context.productCTX) {
23+
const ctx = context.productCTX;
24+
setProduct(ctx);
25+
repo_api.File(
26+
ctx.owner.username,
27+
ctx.repo.name,
28+
"README.md",
29+
ctx.data.hash,
30+
)
31+
.then(res=>{
32+
if (res.status === 200) {
33+
setReadme(res.data.toString())
34+
}
35+
})
36+
}
37+
}, []);
38+
39+
return(
40+
<>
41+
{
42+
Product && <div className={'market-page'}>
43+
<div className="market-page-title">
44+
<div style={{
45+
display: "flex"
46+
}}>
47+
<img src={Product.owner.avatar || ""} alt={Product.data.name}/>
48+
<div>
49+
<h1>{Product.data.name}</h1>
50+
<span>{Product.data.description}</span>
51+
</div>
52+
</div>
53+
54+
55+
</div>
56+
<div className={'market-page-body'}>
57+
<div className={'market-page-markdown'}>
58+
<ReactMarkdown
59+
rehypePlugins={[rehypeRaw]}
60+
components={{
61+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
62+
// @ts-expect-error
63+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
64+
code: ({node, inline, className, children, ...props}) => {
65+
const match = /language-(\w+)/.exec(className || '');
66+
return !inline && match ? (
67+
<div className="code-block">
68+
<code className={className}>{children}</code>
69+
</div>
70+
) : (
71+
<code className={className} {...props}>
72+
{children}
73+
</code>
74+
);
75+
}
76+
}}
77+
>
78+
{Readme}
79+
</ReactMarkdown>
80+
</div>
81+
<div className="market-page-info">
82+
<div style={{
83+
display: "flex",
84+
alignItems: "center",
85+
justifyContent: "space-between",
86+
flexDirection: "row"
87+
}}>
88+
Price: {
89+
Product.data.price === 0 ? <a style={{
90+
color: "green"
91+
}}>Free</a> : <a style={{
92+
color: "red"
93+
}}>{Product.data.price}</a>
94+
}
95+
<div className="access">
96+
<Button>Get Access</Button>
97+
</div>
98+
</div>
99+
100+
<div>
101+
License: {Product.data.license}
102+
</div>
103+
<div className="hover" onClick={()=>{
104+
nav(`/r/${Product?.owner.username}/${Product?.repo.name}?tab=intro`)
105+
}}>
106+
Repo: {Product.owner.username} / {Product.repo.name}
107+
</div>
108+
<div className="hover" onClick={()=>{
109+
nav(`/u/${Product?.owner.username}`)
110+
}}>
111+
Owner: {Product.owner.username}
112+
</div>
113+
<div>
114+
Created At: {Product.data.created_at.toString().split(".")[0]}
115+
</div>
116+
<div style={{
117+
display: 'flex',
118+
gap: '5px'
119+
}}>
120+
Tags: {
121+
Product.data.type.split(",").map((value, index)=>{
122+
return <span style={{
123+
color: "black",
124+
backgroundColor: "orangered",
125+
borderRadius: "5px",
126+
padding: "0 5px",
127+
}} key={index}>{value}</span>
128+
})
129+
}
130+
</div>
131+
</div>
132+
</div>
133+
</div>
134+
}
135+
</>
136+
)
137+
}

src/app/(default)/marketplace/page.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
'use client'
22

33
import {MarketTitle} from "@/component/market/marketTitle";
4-
import {useState} from "react";
4+
import {useEffect, useState} from "react";
55
import {MarketItem} from "@/component/market/marketItem";
66
import {ProductList, ProductListParam} from "@/server/types";
77
import {ProductApi} from "@/server/ProductApi";
88
import {AppWrite} from "@/server/Client";
9-
109
export default function MarketPlacePage() {
1110
const [ItemData, setItemData] = useState<ProductList[]>([]);
1211
const [Query, setQuery] = useState<ProductListParam>({
@@ -20,6 +19,7 @@ export default function MarketPlacePage() {
2019

2120

2221

22+
2323
const InitData = async (query: ProductListParam) => {
2424
try {
2525
const response = await api.List(query.limit, query.page, query.order, query.search);
@@ -39,7 +39,9 @@ export default function MarketPlacePage() {
3939
setQuery(newQuery);
4040
InitData(newQuery);
4141
};
42-
InitData(Query);
42+
useEffect(() => {
43+
InitData(Query);
44+
}, []);
4345

4446
return (
4547
<div className="market">

src/component/market/marketItem.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {ProductList} from "@/server/types";
2+
import {useRouter} from "next/navigation";
23

34
export interface MarketItemProps {
45
data: ProductList;
@@ -8,8 +9,11 @@ export interface MarketItemProps {
89
export const MarketItem = ({data}: MarketItemProps) => {
910
const product = data.data;
1011
const owner = data.owner;
12+
const nav = useRouter().replace;
1113
return (
12-
<div className="market-item">
14+
<div className="market-item" onClick={()=>{
15+
nav(`/m/${product.uid}`)
16+
}}>
1317

1418
<div className="market-item-image">
1519
<div style={{

src/server/ProductApi.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class ProductApi extends HttpClient {
1414
})
1515
}
1616
async Info(uid: string) {
17-
return await this.get<string>(`/product/info/${uid}`, {})
17+
return await this.get<string>(`/product/info/${uid}`)
1818
}
1919
async Download(uid: string) {
2020
return await this.get<string>(`/product/down/${uid}`, {})

src/store/usePageContext.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ export interface PageState {
1818
user: UserDashBored,
1919
username: string
2020
};
21+
productCTX?: ProductList,
2122
reset: () => void;
2223
setTab: (tab: string) => void;
2324
setUrl: (url: string) => void;
2425
setRepoCtx: (repoCtx: { repo: Repository, owner: string, repoName: string, repoInfo: RepoInfo, branches: BranchModel[],products: ProductList[]}) => void;
2526
setUserCtx: (userCtx: { user: UserDashBored, username: string }) => void;
2627
setUrlAndTab: (url: string, tab: string) => void;
28+
setProduct: (product: ProductList) => void,
2729
}
2830

2931

@@ -36,12 +38,14 @@ const usePageContext = create<PageState>()(
3638
tab: '',
3739
repoCtx: undefined,
3840
userCtx: undefined,
41+
productCTX: undefined,
3942
reset: () => set({url: '', tab: ''}),
4043
setTab: (tab: string) => set({tab: tab}),
4144
setUrl: (url: string) => set({url: url}),
4245
setRepoCtx: (repoCtx: { repo: Repository, owner: string, repoName: string , repoInfo: RepoInfo, branches: BranchModel[],products: ProductList[]}) => set({repoCtx: repoCtx}),
4346
setUserCtx: (userCtx: { user: UserDashBored, username: string }) => set({userCtx: userCtx}),
4447
setUrlAndTab: (url: string, tab: string) => set({url: url, tab: tab}),
48+
setProduct: (product: ProductList) => set({productCTX: product}),
4549
}
4650
),
4751
{

src/style/product.css

+44
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,48 @@
4545
}
4646
}
4747
}
48+
}
49+
50+
.market-page {
51+
display: flex;
52+
flex-direction: column;
53+
align-content: center;
54+
.market-page-title {
55+
display: flex;
56+
justify-content: space-between;
57+
align-content: center;
58+
img {
59+
margin-top: 2rem;
60+
margin-left: 2rem;
61+
height: 3rem;
62+
width: 3rem;
63+
}
64+
}
65+
.access {
66+
margin-right: 2rem;
67+
}
68+
.market-page-body {
69+
display: grid;
70+
grid-template-columns: 4fr 300px;
71+
.market-page-markdown {
72+
margin-top: 2rem;
73+
padding: 1rem;
74+
border: 1px #d6d6d6 solid;
75+
}
76+
.market-page-info {
77+
display: flex;
78+
flex-direction: column;
79+
justify-content: flex-start;
80+
gap: 1rem;
81+
padding: 2rem;
82+
max-height: 50vh;
83+
margin-top: 2rem;
84+
div {
85+
cursor: pointer;
86+
}
87+
.hover:hover {
88+
color: #f15108;
89+
}
90+
}
91+
}
4892
}

0 commit comments

Comments
 (0)