mirror of
https://github.com/labring/sealos.git
synced 2026-10-29 17:25:58 +08:00
Stripe (#3640)
* feat(frontend/costcenter): pay for i18n * fix:update lock * perf:env
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
**/dist
|
||||
**/.vscode
|
||||
**/.DS_Store
|
||||
**/node_modules
|
||||
**/dist
|
||||
**/.vscode
|
||||
**/.DS_Store
|
||||
**/node_modules
|
||||
**/.next
|
||||
+20
-20
@@ -1,21 +1,21 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
|
||||
Generated
+918
-1947
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": 0
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/public/trainData/
|
||||
/.vscode/
|
||||
platform.json
|
||||
.yalc/
|
||||
yalc.lock
|
||||
data/*.local
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/public/trainData/
|
||||
/.vscode/
|
||||
platform.json
|
||||
.yalc/
|
||||
yalc.lock
|
||||
data/*.local
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dist
|
||||
.vscode
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
.vscode
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
# sealos app launchpad
|
||||
|
||||
## project tree
|
||||
```bash
|
||||
.
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── deploy
|
||||
│ └── manifests
|
||||
│ └── frontend.yaml
|
||||
├── next-env.d.ts
|
||||
├── next.config.js
|
||||
├── package.json
|
||||
├── pnpm-lock.yaml
|
||||
├── public
|
||||
│ └── favicon.ico
|
||||
├── src
|
||||
│ ├── api # FE api
|
||||
│ ├── components # global components
|
||||
│ │ ├── AppStatusTag
|
||||
│ │ ├── ButtonGroup
|
||||
│ │ ├── FormControl
|
||||
│ │ ├── Icon
|
||||
│ │ │ ├── icons # svg icon
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PodLineChart
|
||||
│ │ ├── RangeInput
|
||||
│ │ ├── RangeSlider
|
||||
│ │ ├── Slider
|
||||
│ │ └── YamlCode
|
||||
│ ├── constants # global constant data
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── editApp.ts
|
||||
│ │ └── theme.ts
|
||||
│ ├── hooks # global hooks
|
||||
│ │ ├── useConfirm.tsx
|
||||
│ │ ├── useLoading.tsx
|
||||
│ │ ├── useScreen.ts
|
||||
│ │ └── useToast.ts
|
||||
│ ├── mock
|
||||
│ ├── pages
|
||||
│ │ ├── 404.tsx
|
||||
│ │ ├── _app.tsx
|
||||
│ │ ├── _document.tsx
|
||||
│ │ ├── api # server api
|
||||
│ │ ├── app
|
||||
│ │ │ ├── detail
|
||||
│ │ │ └── edit
|
||||
│ │ └── apps
|
||||
│ │ └── index.tsx
|
||||
│ ├── services # server function
|
||||
│ │ ├── backend
|
||||
│ │ │ ├── auth.ts
|
||||
│ │ │ ├── kubernetes.ts
|
||||
│ │ │ └── response.ts
|
||||
│ │ ├── error.ts
|
||||
│ │ ├── kubernet.ts
|
||||
│ │ └── request.ts
|
||||
│ ├── store # FE store
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── global.ts
|
||||
│ │ └── static.ts
|
||||
│ ├── styles
|
||||
│ │ └── reset.scss
|
||||
│ ├── types
|
||||
│ │ ├── app.d.ts
|
||||
│ │ ├── index.d.ts
|
||||
│ │ └── user.d.ts
|
||||
│ └── utils
|
||||
│ ├── adapt.ts # format api data
|
||||
│ ├── deployYaml2Json.ts # form data to yaml
|
||||
│ ├── tools.ts
|
||||
│ └── user.ts
|
||||
└── tsconfig.json
|
||||
# sealos app launchpad
|
||||
|
||||
## project tree
|
||||
```bash
|
||||
.
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── deploy
|
||||
│ └── manifests
|
||||
│ └── frontend.yaml
|
||||
├── next-env.d.ts
|
||||
├── next.config.js
|
||||
├── package.json
|
||||
├── pnpm-lock.yaml
|
||||
├── public
|
||||
│ └── favicon.ico
|
||||
├── src
|
||||
│ ├── api # FE api
|
||||
│ ├── components # global components
|
||||
│ │ ├── AppStatusTag
|
||||
│ │ ├── ButtonGroup
|
||||
│ │ ├── FormControl
|
||||
│ │ ├── Icon
|
||||
│ │ │ ├── icons # svg icon
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PodLineChart
|
||||
│ │ ├── RangeInput
|
||||
│ │ ├── RangeSlider
|
||||
│ │ ├── Slider
|
||||
│ │ └── YamlCode
|
||||
│ ├── constants # global constant data
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── editApp.ts
|
||||
│ │ └── theme.ts
|
||||
│ ├── hooks # global hooks
|
||||
│ │ ├── useConfirm.tsx
|
||||
│ │ ├── useLoading.tsx
|
||||
│ │ ├── useScreen.ts
|
||||
│ │ └── useToast.ts
|
||||
│ ├── mock
|
||||
│ ├── pages
|
||||
│ │ ├── 404.tsx
|
||||
│ │ ├── _app.tsx
|
||||
│ │ ├── _document.tsx
|
||||
│ │ ├── api # server api
|
||||
│ │ ├── app
|
||||
│ │ │ ├── detail
|
||||
│ │ │ └── edit
|
||||
│ │ └── apps
|
||||
│ │ └── index.tsx
|
||||
│ ├── services # server function
|
||||
│ │ ├── backend
|
||||
│ │ │ ├── auth.ts
|
||||
│ │ │ ├── kubernetes.ts
|
||||
│ │ │ └── response.ts
|
||||
│ │ ├── error.ts
|
||||
│ │ ├── kubernet.ts
|
||||
│ │ └── request.ts
|
||||
│ ├── store # FE store
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── global.ts
|
||||
│ │ └── static.ts
|
||||
│ ├── styles
|
||||
│ │ └── reset.scss
|
||||
│ ├── types
|
||||
│ │ ├── app.d.ts
|
||||
│ │ ├── index.d.ts
|
||||
│ │ └── user.d.ts
|
||||
│ └── utils
|
||||
│ ├── adapt.ts # format api data
|
||||
│ ├── deployYaml2Json.ts # form data to yaml
|
||||
│ ├── tools.ts
|
||||
│ └── user.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
@@ -46,7 +46,7 @@ export const valuationMap: Record<string, number> = {
|
||||
};
|
||||
|
||||
const gpuCrName = 'node-gpu-info';
|
||||
const gpuCrNS = 'node-system'
|
||||
const gpuCrNS = 'node-system';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
# for open GPU measure
|
||||
# GPU_ENABLED="true"
|
||||
|
||||
# for Stripe pay
|
||||
# STRIPE_PUB
|
||||
# for open invoice
|
||||
# INVOICE_ENABLED="true"
|
||||
# MONGODB_URI
|
||||
|
||||
@@ -26,7 +26,7 @@ metadata:
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||
nginx.ingress.kubernetes.io/configuration-snippet: |
|
||||
more_clear_headers "X-Frame-Options:";
|
||||
more_set_headers "Content-Security-Policy: default-src * blob: data: *.{{ .cloudDomain }} {{ .cloudDomain }}; img-src * data: blob: resource: *.{{ .cloudDomain }} {{ .cloudDomain }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }} {{ .cloudDomain }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }} {{ .cloudDomain }} resource: *.baidu.com *.bdstatic.com; frame-src 'self' *.{{ .cloudDomain }} {{ .cloudDomain }} mailto: tel: weixin: mtt: *.baidu.com; frame-ancestors 'self' https://{{ .cloudDomain }} https://*.{{ .cloudDomain }}";
|
||||
more_set_headers "Content-Security-Policy: default-src * blob: data: *.{{ .cloudDomain }} {{ .cloudDomain }}; img-src * data: blob: resource: *.{{ .cloudDomain }} {{ .cloudDomain }}; connect-src * wss: blob: resource:; style-src 'self' 'unsafe-inline' blob: *.{{ .cloudDomain }} {{ .cloudDomain }} resource:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: *.{{ .cloudDomain }} {{ .cloudDomain }} resource: *.baidu.com *.bdstatic.com https://js.stripe.com; frame-src 'self' *.{{ .cloudDomain }} {{ .cloudDomain }} mailto: tel: weixin: mtt: *.baidu.com https://js.stripe.com; frame-ancestors 'self' https://{{ .cloudDomain }} https://*.{{ .cloudDomain }}";
|
||||
more_set_headers "X-Xss-Protection: 1; mode=block";
|
||||
|
||||
if ($request_uri ~* \.(js|css|gif|jpe?g|png)) {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
const { i18n } = require('./next-i18next.config');
|
||||
const path = require('path');
|
||||
const { i18n } = require('./next-i18next.config');
|
||||
const ContentSecurityPolicy = `
|
||||
connect-src 'self' https://checkout.stripe.com;
|
||||
frame-src 'self' https://js.stripe.com;
|
||||
script-src 'self' https://js.stripe.com 'unsafe-inline' 'unsafe-eval';
|
||||
style-src 'self' 'unsafe-inline';
|
||||
font-src 'self';
|
||||
`;
|
||||
// https://checkout.stripe.com
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
i18n,
|
||||
@@ -9,6 +17,29 @@ const nextConfig = {
|
||||
experimental: {
|
||||
// this includes files from the monorepo base two directories up
|
||||
outputFileTracingRoot: path.join(__dirname, '../../')
|
||||
},
|
||||
async headers() {
|
||||
console.log(ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim());
|
||||
return [
|
||||
{
|
||||
source: '/',
|
||||
headers: [
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
source: '/cost_overview',
|
||||
headers: [
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@kubernetes/client-node": "0.18.0",
|
||||
"@stripe/stripe-js": "^1.54.2",
|
||||
"@tanstack/query-sync-storage-persister": "^4.29.25",
|
||||
"@tanstack/react-query": "^4.29.25",
|
||||
"@tanstack/react-query-persist-client": "^4.29.25",
|
||||
|
||||
@@ -131,5 +131,7 @@
|
||||
"Invoice Content": "Invoice Content",
|
||||
"Electronic Computer Service Fee": "Electronic Computer Service Fee",
|
||||
"Contact Information": "Contact Information"
|
||||
}
|
||||
},
|
||||
"pay with stripe": "Pay With Stripe",
|
||||
"pay with wechat": "Pay With Wechat"
|
||||
}
|
||||
@@ -131,5 +131,7 @@
|
||||
"Electronic Computer Service Fee": "电子计算机服务费",
|
||||
"Invoice Details": "发票明细",
|
||||
"Contact Information": "联系方式"
|
||||
}
|
||||
},
|
||||
"pay with wechat": "微信支付",
|
||||
"pay with stripe": "Stripe 支付"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.80499 1.74001C4.12471 1.74001 3.47229 2.01025 2.99126 2.49128C2.51023 2.97231 2.23999 3.62473 2.23999 4.30501L2.23999 19.695C2.23999 20.3753 2.51023 21.0277 2.99126 21.5087C3.47229 21.9898 4.12471 22.26 4.80499 22.26H20.195C20.8753 22.26 21.5277 21.9898 22.0087 21.5087C22.4897 21.0277 22.76 20.3753 22.76 19.695V4.30501C22.76 3.62473 22.4897 2.97231 22.0087 2.49128C21.5277 2.01025 20.8753 1.74001 20.195 1.74001H4.80499ZM12.7898 8.64627C12.0409 8.64627 11.5881 8.8566 11.5881 9.40679C11.5881 10.007 12.3666 10.2712 13.3323 10.5995C14.9072 11.1318 16.9798 11.8346 16.9887 14.438C16.9887 16.9607 14.9675 18.4125 12.0255 18.4125C10.6981 18.4083 9.38534 18.1352 8.16642 17.6097V14.2546C9.35402 14.9036 10.8533 15.3832 12.0267 15.3832C12.8181 15.3832 13.3836 15.1716 13.3836 14.5227C13.3836 13.8583 12.5397 13.5544 11.5202 13.1876C9.96834 12.6284 8.01124 11.9231 8.01124 9.57608C8.01124 7.08162 9.9196 5.58751 12.7898 5.58751C13.9893 5.57646 15.1798 5.79634 16.2962 6.23517V9.54787C15.2215 8.97074 13.8646 8.64627 12.7898 8.64627Z" fill="#9893FF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.0109 6.03363C15.0109 6.03363 14.7533 4.79202 13.4005 4.57209C13.4005 4.57209 12.7499 1.79861 10.2988 2.86222C10.2988 2.86222 8.50895 0.321821 6.6064 2.86222C6.6064 2.86222 4.3372 1.66469 3.39566 4.57636C3.39566 4.57636 1.74999 4.61839 1.73661 6.86748C1.72324 9.11659 4.96431 11.5493 5.42673 11.7616C5.42673 11.7616 4.40925 12.8655 5.65566 13.632C7.3812 14.5362 10.6415 14.2071 11.4358 13.4981C11.9283 13.0586 11.8898 12.3834 11.4358 11.8285C11.4339 11.8304 15.6109 9.37285 15.0109 6.03363ZM12.2595 4.18442C11.6313 3.16341 10.4014 3.83814 10.4014 3.83814C10.8606 7.55094 9.40345 11.1801 9.40345 11.1801C10.7846 10.7479 13.0174 5.41596 12.2595 4.18442ZM8.28708 2.73063C8.28708 2.73063 7.2848 2.79384 7.11572 4.16811C6.94664 5.54238 7.34795 10.8784 8.4195 11.2637C8.4195 11.2637 9.17528 11.4615 9.69476 6.78818C10.3813 2.24844 8.28708 2.73063 8.28708 2.73063ZM6.41983 3.83814C6.41983 3.83814 5.35106 3.09129 4.58555 4.18442C3.82977 5.41596 6.06251 10.7479 7.44166 11.1801C7.44166 11.1801 6.00868 7.93857 6.41983 3.83814ZM3.59458 5.50301C3.59458 5.50301 2.55069 5.50301 2.66247 6.95743C2.80304 8.78638 5.13881 10.7271 6.00868 11.0493C6.00663 11.0493 4.05135 8.31466 3.59458 5.50301ZM5.96882 13.005C6.26016 13.5167 9.61245 13.6711 10.9183 13.094C10.9183 13.094 11.3283 12.6776 10.7011 11.8474C10.6787 11.8177 10.4516 11.8396 10.3413 11.8334C10.231 11.8272 10.1226 11.8003 9.99423 11.774C9.99423 11.774 9.59059 12.0633 8.95323 11.8474C8.95323 11.8474 8.50283 12.0991 7.97744 11.8474C7.97744 11.8474 7.32729 12.0693 6.93645 11.774C6.93645 11.774 6.66195 11.8488 6.25919 11.774C6.25919 11.774 5.68592 12.5083 5.96882 13.005ZM13.2433 5.50601C12.7428 8.62454 10.8606 11.0519 10.8606 11.0519C11.9987 10.8108 13.9363 8.41185 14.1867 6.95743C14.4371 5.50301 13.2433 5.50601 13.2433 5.50601Z" fill="#24282C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,30 @@
|
||||
import { Spinner } from '@chakra-ui/react';
|
||||
// import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js';
|
||||
import { FormEvent } from 'react';
|
||||
import useEnvStore from '@/stores/env';
|
||||
const CheckoutForm = (props: { url: string; sessionId: string }) => {
|
||||
// const stripe = useStripe();
|
||||
// const elements = useElements();
|
||||
|
||||
const stripePromise = useEnvStore((s) => s.stripePromise);
|
||||
const handleSubmit = async (event: FormEvent) => {
|
||||
// We don't want to let default form submission happen here,
|
||||
// which would refresh the page.
|
||||
event.preventDefault();
|
||||
if (!stripePromise) {
|
||||
// Stripe.js hasn't yet loaded.
|
||||
// Make sure to disable form submission until Stripe.js has loaded.
|
||||
return;
|
||||
}
|
||||
|
||||
const res1 = await stripePromise;
|
||||
if (!res1) return;
|
||||
const res = await res1.redirectToCheckout({
|
||||
sessionId: props.sessionId
|
||||
});
|
||||
};
|
||||
|
||||
return <Spinner />;
|
||||
};
|
||||
|
||||
export default CheckoutForm;
|
||||
@@ -5,6 +5,7 @@ import lineUp from '@/assert/lineUp.svg';
|
||||
import { Flex, Img, Table, TableContainer, Tbody, Td, Text, Th, Thead, Tr } from '@chakra-ui/react';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { formatMoney } from '@/utils/format';
|
||||
import shellCoin from '@/assert/shell_coin.svg';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import useEnvStore from '@/stores/env';
|
||||
export function BillingTable({ data }: { data: BillingItem[] }) {
|
||||
@@ -80,13 +81,56 @@ export function BillingTable({ data }: { data: BillingItem[] }) {
|
||||
</Flex>
|
||||
</Td>
|
||||
|
||||
<Td>{!item.type ? '¥' + formatMoney(item.costs?.cpu || 0) : '-'}</Td>
|
||||
<Td>{!item.type ? '¥' + formatMoney(item.costs?.memory || 0) : '-'}</Td>
|
||||
<Td>{!item.type ? '¥' + formatMoney(item.costs?.storage || 0) : '-'}</Td>
|
||||
<Td>
|
||||
{!item.type ? (
|
||||
<Flex>
|
||||
<Img src={shellCoin.src} mr="4px" />
|
||||
<span>{formatMoney(item.costs?.cpu || 0)}</span>
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{!item.type ? (
|
||||
<Flex>
|
||||
<Img src={shellCoin.src} mr="4px" />
|
||||
<span>{formatMoney(item.costs?.memory || 0)}</span>{' '}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{!item.type ? (
|
||||
<Flex>
|
||||
<Img src={shellCoin.src} mr="4px" />
|
||||
<span>{formatMoney(item.costs?.storage || 0)}</span>{' '}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
{gpuEnabled && (
|
||||
<Td>{!item.type ? '¥' + formatMoney(item.costs?.gpu || 0) : '-'}</Td>
|
||||
<Td>
|
||||
{!item.type ? (
|
||||
<Flex>
|
||||
<Img src={shellCoin.src} mr="4px" />
|
||||
<span>{formatMoney(item.costs?.gpu || 0)}</span>{' '}
|
||||
</Flex>
|
||||
) : (
|
||||
'-'
|
||||
)}
|
||||
</Td>
|
||||
)}
|
||||
<Td>{'¥' + formatMoney(item.amount)}</Td>
|
||||
<Td>
|
||||
{
|
||||
<Flex>
|
||||
<Img src={shellCoin.src} mr="4px" />
|
||||
<span>{formatMoney(item.amount)}</span>
|
||||
</Flex>
|
||||
}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
||||
import { formatMoney } from '@/utils/format';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import useBillingData from '@/hooks/useBillingData';
|
||||
|
||||
import shellcoin from '@/assert/shell_coin.svg';
|
||||
export function Buget() {
|
||||
const { t } = useTranslation();
|
||||
const { data } = useBillingData();
|
||||
@@ -26,8 +26,8 @@ export function Buget() {
|
||||
[data]
|
||||
);
|
||||
const list = [
|
||||
{ title: 'Deduction', src: down_icon.src, value: '¥' + formatMoney(_out) },
|
||||
{ title: 'Charge', src: up_icon.src, value: '¥' + formatMoney(_in) }
|
||||
{ title: 'Deduction', src: down_icon.src, value: formatMoney(_out) },
|
||||
{ title: 'Charge', src: up_icon.src, value: formatMoney(_in) }
|
||||
];
|
||||
return (
|
||||
<Flex direction={'column'} mb={'34px'}>
|
||||
@@ -51,9 +51,12 @@ export function Buget() {
|
||||
<Text fontSize={'12px'} mt={'6px'}>
|
||||
{t(v.title)}
|
||||
</Text>
|
||||
<Text fontWeight="500" fontSize="16px" mt={'8px'}>
|
||||
{v.value}
|
||||
</Text>
|
||||
<Flex mt="8px">
|
||||
<Img src={shellcoin.src} mr="4px" />
|
||||
<Text fontWeight="500" fontSize="16px">
|
||||
{v.value}
|
||||
</Text>
|
||||
</Flex>
|
||||
</CardBody>
|
||||
</Card>
|
||||
))}
|
||||
|
||||
+221
-123
@@ -1,4 +1,4 @@
|
||||
import request from '@/service/request';
|
||||
import shellcoin from '@/assert/shell_coin.svg';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -20,16 +20,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import {
|
||||
forwardRef,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
|
||||
import wechat_icon from '@/assert/ic_baseline-wechat.svg';
|
||||
import vector from '@/assert/Vector.svg';
|
||||
import { deFormatMoney, formatMoney } from '@/utils/format';
|
||||
@@ -38,7 +29,150 @@ import { getFavorable } from '@/utils/favorable';
|
||||
import { ApiResp } from '@/types/api';
|
||||
import { Pay, Payment } from '@/types/payment';
|
||||
import OuterLink from '@/components/outerLink';
|
||||
import stripe_icon from '@/assert/bi_stripe.svg';
|
||||
import { Spinner } from '@chakra-ui/react';
|
||||
import { Stripe } from '@stripe/stripe-js';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
const StripeForm = (props: {
|
||||
tradeNO?: string;
|
||||
complete: number;
|
||||
stripePromise: Promise<Stripe | null>;
|
||||
}) => {
|
||||
const stripePromise = props.stripePromise;
|
||||
const sessionId = props.tradeNO;
|
||||
const complete = props.complete;
|
||||
useEffect(() => {
|
||||
// console.log('new')
|
||||
if (stripePromise && sessionId)
|
||||
(async () => {
|
||||
try {
|
||||
const res1 = await stripePromise;
|
||||
if (!res1) return;
|
||||
if (complete !== 2) return;
|
||||
await res1.redirectToCheckout({
|
||||
sessionId
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}, [sessionId, stripePromise, complete]);
|
||||
return (
|
||||
<Flex w={'100%'} flex={1} align={'center'} justify={'center'}>
|
||||
<Spinner size={'xl'} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
function WechatPayment(props: { complete: number; codeURL?: string; tradeNO?: string }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
px="37px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
mt={'135px'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Flex
|
||||
width={'267px'}
|
||||
height={'295px'}
|
||||
direction={'column'}
|
||||
align="center"
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Text color="#7B838B" mb="8px" textAlign="center">
|
||||
{t('Scan with WeChat')}
|
||||
</Text>
|
||||
{props.complete === 2 && !!props.codeURL ? (
|
||||
<QRCodeSVG
|
||||
size={185}
|
||||
value={props.codeURL}
|
||||
style={{ margin: '0 auto' }}
|
||||
imageSettings={{
|
||||
// 二维码中间的logo图片
|
||||
src: wechat_icon.src,
|
||||
height: 40,
|
||||
width: 40,
|
||||
excavate: true // 中间图片所在的位置是否镂空
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box>waiting...</Box>
|
||||
)}
|
||||
<Box mt="8px">
|
||||
<Text color="#717D8A" fontSize="12px" fontWeight="normal">
|
||||
{t('Order Number')}: {props.tradeNO || ''}
|
||||
</Text>
|
||||
<Text color="#717D8A" fontSize="12px">
|
||||
{t('Payment Result')}:{props.complete === 3 ? t('Payment Successful') : t('In Payment')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
const BonusBox = (props: {
|
||||
onClick: () => void;
|
||||
selected: boolean;
|
||||
bouns: number;
|
||||
amount: number;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex
|
||||
width="140px"
|
||||
height="92px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
{...(props.selected
|
||||
? {
|
||||
color: '#36ADEF',
|
||||
border: '1.5px solid #36ADEF'
|
||||
}
|
||||
: {
|
||||
border: '1px solid #EFF0F1'
|
||||
})}
|
||||
bg={'#f4f6f8'}
|
||||
borderRadius="4px"
|
||||
position={'relative'}
|
||||
flexGrow="0"
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
props.onClick();
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
position={'absolute'}
|
||||
display={'inline-block'}
|
||||
minW={'max-content'}
|
||||
left="78px"
|
||||
top="4px"
|
||||
px={'10px'}
|
||||
color={'#A558C9'}
|
||||
background="#EDDEF4"
|
||||
borderRadius="10px 10px 10px 0px"
|
||||
zIndex={'99'}
|
||||
fontStyle="normal"
|
||||
fontWeight="500"
|
||||
fontSize="12px"
|
||||
>
|
||||
{t('Bonus')} {props.bouns}
|
||||
</Text>
|
||||
<Flex align={'center'}>
|
||||
<Img src={shellcoin.src} w="24px" h="24px" mr="4px" />
|
||||
<Text fontStyle="normal" fontWeight="500" fontSize="24px">
|
||||
{props.amount}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
const RechargeModal = forwardRef(
|
||||
(
|
||||
props: {
|
||||
@@ -48,12 +182,15 @@ const RechargeModal = forwardRef(
|
||||
onCreatedError?: () => void;
|
||||
onCancel?: () => void;
|
||||
balance: number;
|
||||
stripePromise: Promise<Stripe | null>;
|
||||
request: AxiosInstance;
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const balance = props.balance || 0;
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose: _onClose } = useDisclosure();
|
||||
const request = props.request;
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
@@ -71,16 +208,18 @@ const RechargeModal = forwardRef(
|
||||
|
||||
// 整个流程跑通需要状态管理, 0 初始态, 1 创建支付单, 2 支付中, 3 支付成功
|
||||
const [complete, setComplete] = useState<0 | 1 | 2 | 3>(0);
|
||||
// 0 是微信,1 是stripe
|
||||
const [payType, setPayType] = useState<'wechat' | 'stripe'>('wechat');
|
||||
// 计费详情
|
||||
const [detail, setDetail] = useState(false);
|
||||
const [paymentName, setPaymentName] = useState('');
|
||||
const [selectAmount, setSelectAmount] = useState(0);
|
||||
|
||||
const toast = useToast();
|
||||
const createPaymentRes = useMutation(
|
||||
() =>
|
||||
request.post<any, ApiResp<Payment>>('/api/account/payment', {
|
||||
amount: deFormatMoney(amount)
|
||||
amount: deFormatMoney(amount),
|
||||
paymentMethod: payType
|
||||
}),
|
||||
{
|
||||
onSuccess(data) {
|
||||
@@ -101,8 +240,8 @@ const RechargeModal = forwardRef(
|
||||
}
|
||||
);
|
||||
|
||||
const { data } = useQuery(
|
||||
['query-charge-res'],
|
||||
const { data, isPreviousData } = useQuery(
|
||||
['query-charge-res', { id: paymentName }],
|
||||
() =>
|
||||
request<any, ApiResp<Pay>>('/api/account/payment/pay', {
|
||||
params: {
|
||||
@@ -115,6 +254,7 @@ const RechargeModal = forwardRef(
|
||||
cacheTime: 0,
|
||||
staleTime: 0,
|
||||
onSuccess(data) {
|
||||
console.log('pay???', data);
|
||||
setTimeout(() => {
|
||||
if (data?.data?.status === 'SUCCESS') {
|
||||
createPaymentRes.reset();
|
||||
@@ -127,9 +267,9 @@ const RechargeModal = forwardRef(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const cancalPay = useCallback(() => {
|
||||
createPaymentRes.reset();
|
||||
console.log('reset?', createPaymentRes);
|
||||
props.onCancel?.();
|
||||
setComplete(0);
|
||||
}, [createPaymentRes]);
|
||||
@@ -175,14 +315,21 @@ const RechargeModal = forwardRef(
|
||||
}
|
||||
}, [steps]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
const handleWechatConfirm = () => {
|
||||
setPayType('wechat');
|
||||
setComplete(1);
|
||||
createPaymentRes.mutate();
|
||||
};
|
||||
|
||||
const handleStripeConfirm = () => {
|
||||
setComplete(1);
|
||||
setPayType('stripe');
|
||||
createPaymentRes.mutate();
|
||||
};
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent maxW="500px" height={'565px'}>
|
||||
<ModalContent maxW="500px" minH={'495'} display={'flex'} flexDir={'column'}>
|
||||
{!detail ? (
|
||||
complete === 0 ? (
|
||||
<>
|
||||
@@ -194,6 +341,7 @@ const RechargeModal = forwardRef(
|
||||
pointerEvents={complete === 0 ? 'auto' : 'none'}
|
||||
pt="4px"
|
||||
mt={'0'}
|
||||
pb="28px"
|
||||
w="500px"
|
||||
px={'24px'}
|
||||
flexDirection="column"
|
||||
@@ -201,11 +349,12 @@ const RechargeModal = forwardRef(
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex align={'center'} alignSelf={'flex-start'} mb={'20px'}>
|
||||
<Text color="#7B838B" fontWeight={'normal'} mr={'24px'}>
|
||||
<Text color="#7B838B" fontWeight={'normal'} mr={'20px'}>
|
||||
{t('Balance')}
|
||||
</Text>
|
||||
<Text mt="4px" color="#24282C" fontSize="24px" fontWeight={'medium'}>
|
||||
¥ {formatMoney(balance).toFixed(2)}
|
||||
<Img src={shellcoin.src} w="24px" h="24px" mr="4px" />
|
||||
<Text color="#24282C" fontSize="24px" fontWeight={'medium'}>
|
||||
{formatMoney(balance).toFixed(2)}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex direction={'column'} mb={'20px'}>
|
||||
@@ -214,51 +363,16 @@ const RechargeModal = forwardRef(
|
||||
</Text>
|
||||
<Flex wrap={'wrap'} gap={'16px'}>
|
||||
{steps.map((amount, index) => (
|
||||
<Flex
|
||||
width="140px"
|
||||
height="92px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
<BonusBox
|
||||
key={index}
|
||||
{...(selectAmount === index
|
||||
? {
|
||||
color: '#36ADEF',
|
||||
border: '1.5px solid #36ADEF'
|
||||
}
|
||||
: {
|
||||
border: '1px solid #EFF0F1'
|
||||
})}
|
||||
bg={'#f4f6f8'}
|
||||
borderRadius="4px"
|
||||
position={'relative'}
|
||||
flexGrow="0"
|
||||
cursor={'pointer'}
|
||||
amount={amount}
|
||||
bouns={getBonus(amount)}
|
||||
onClick={() => {
|
||||
setSelectAmount(index);
|
||||
setAmount(amount);
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
position={'absolute'}
|
||||
display={'inline-block'}
|
||||
minW={'max-content'}
|
||||
left="78px"
|
||||
top="4px"
|
||||
px={'10px'}
|
||||
color={'#A558C9'}
|
||||
background="#EDDEF4"
|
||||
borderRadius="10px 10px 10px 0px"
|
||||
zIndex={'99'}
|
||||
fontStyle="normal"
|
||||
fontWeight="500"
|
||||
fontSize="12px"
|
||||
>
|
||||
{t('Bonus')}¥{getBonus(amount)}
|
||||
</Text>
|
||||
<Text fontStyle="normal" fontWeight="500" fontSize="24px">
|
||||
¥{amount}
|
||||
</Text>
|
||||
</Flex>
|
||||
selected={selectAmount === index}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -285,7 +399,7 @@ const RechargeModal = forwardRef(
|
||||
variant={'unstyled'}
|
||||
onChange={(str, v) => (str.trim() ? setAmount(v) : setAmount(0))}
|
||||
>
|
||||
<Text mr={'4px'}>¥</Text>
|
||||
<Img src={shellcoin.src} w="16px" h="16px" mr="4px" />
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper>
|
||||
@@ -306,10 +420,10 @@ const RechargeModal = forwardRef(
|
||||
fontStyle="normal"
|
||||
fontWeight="500"
|
||||
fontSize="12px"
|
||||
mr="4px"
|
||||
>
|
||||
{t('Bonus')}
|
||||
{t('Bonus')} {getBonus(amount)}
|
||||
</Text>
|
||||
<Text>¥{getBonus(amount)}</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
alignSelf={'flex-start'}
|
||||
@@ -319,16 +433,36 @@ const RechargeModal = forwardRef(
|
||||
>
|
||||
<OuterLink text={t('View Discount Rules')}></OuterLink>
|
||||
</Flex>
|
||||
<Button
|
||||
size="primary"
|
||||
variant="primary"
|
||||
mt="20px"
|
||||
w="full"
|
||||
h="48px"
|
||||
onClick={() => handleConfirm()}
|
||||
>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
<Flex gap={'16px'} width={'full'}>
|
||||
<Button
|
||||
size="primary"
|
||||
variant="primary"
|
||||
mt="20px"
|
||||
w="full"
|
||||
h="auto"
|
||||
py="14px"
|
||||
px="34px"
|
||||
onClick={() => {
|
||||
handleStripeConfirm();
|
||||
}}
|
||||
>
|
||||
<Img src={stripe_icon.src} mr="2px" w="24px" h="24px" />
|
||||
<Text>{t('pay with stripe')}</Text>
|
||||
</Button>
|
||||
<Button
|
||||
size="primary"
|
||||
variant="primary"
|
||||
mt="20px"
|
||||
w="full"
|
||||
h="auto"
|
||||
py="14px"
|
||||
px="34px"
|
||||
onClick={() => handleWechatConfirm()}
|
||||
>
|
||||
<Img src={wechat_icon.src} mr="2px" w="24px" h="24px" />
|
||||
<Text>{t('pay with wechat')}</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
) : (
|
||||
@@ -357,55 +491,19 @@ const RechargeModal = forwardRef(
|
||||
<Text>{t('Recharge Amount')}</Text>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton top={'24px'} right={'24px'} />
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
px="37px"
|
||||
justify={'center'}
|
||||
align={'center'}
|
||||
mt={'135px'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Flex
|
||||
width={'267px'}
|
||||
height={'295px'}
|
||||
direction={'column'}
|
||||
align="center"
|
||||
justify={'space-between'}
|
||||
>
|
||||
<Text color="#7B838B" mb="8px" textAlign="center">
|
||||
{t('Scan with WeChat')}
|
||||
</Text>
|
||||
{complete === 2 && !!data?.data?.codeURL ? (
|
||||
<QRCodeSVG
|
||||
size={185}
|
||||
value={data?.data?.codeURL}
|
||||
style={{ margin: '0 auto' }}
|
||||
imageSettings={{
|
||||
// 二维码中间的logo图片
|
||||
src: wechat_icon.src,
|
||||
height: 40,
|
||||
width: 40,
|
||||
excavate: true // 中间图片所在的位置是否镂空
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box>waiting...</Box>
|
||||
)}
|
||||
<Box mt="8px">
|
||||
<Text color="#717D8A" fontSize="12px" fontWeight="normal">
|
||||
{t('Order Number')}: {data?.data?.tradeNO}
|
||||
</Text>
|
||||
|
||||
<Text color="#717D8A" fontSize="12px">
|
||||
{t('Payment Result')}:
|
||||
{complete === 3 ? t('Payment Successful') : t('In Payment')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{payType === 'wechat' ? (
|
||||
<WechatPayment
|
||||
complete={complete}
|
||||
codeURL={!isPreviousData ? data?.data?.codeURL : undefined}
|
||||
tradeNO={!isPreviousData ? data?.data?.tradeNO : undefined}
|
||||
/>
|
||||
) : (
|
||||
<StripeForm
|
||||
tradeNO={!isPreviousData ? data?.data?.tradeNO : undefined}
|
||||
complete={complete}
|
||||
stripePromise={props.stripePromise}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
|
||||
+4
-2
@@ -1,4 +1,5 @@
|
||||
import request from '@/service/request';
|
||||
import shellcoin from '@/assert/shell_coin.svg';
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
@@ -215,7 +216,7 @@ const TransferModal = forwardRef(
|
||||
onChange={(str, v) => (str.trim() ? setAmount(v) : setAmount(0))}
|
||||
isDisabled={mutation.isLoading}
|
||||
>
|
||||
<Text mr={'4px'}>¥</Text>
|
||||
<Img mr={'4px'} src={shellcoin.src} w="16px" h="16px" />
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper>
|
||||
@@ -230,8 +231,9 @@ const TransferModal = forwardRef(
|
||||
<Text fontSize="12px" mr={'12px'}>
|
||||
{t('Balance')}
|
||||
</Text>
|
||||
<Img mr={'4px'} src={shellcoin.src} w="16px" h="16px" />
|
||||
<Text color="rgba(33, 155, 244, 1)" fontSize={'16px'}>
|
||||
¥ {formatMoney(balance).toFixed(2)}
|
||||
{formatMoney(balance).toFixed(2)}
|
||||
</Text>
|
||||
<Button
|
||||
size="primary"
|
||||
|
||||
@@ -156,7 +156,7 @@ export default function Trend({ data }: { data: BillingItem[] }) {
|
||||
totalCostP.style.fontWeight = '500';
|
||||
totalCostP.style.fontSize = '14px';
|
||||
totalCostP.style.lineHeight = '150%';
|
||||
totalCostP.textContent = `${t('Total Cost')} : ¥${totalCost}`;
|
||||
totalCostP.textContent = `${t('Total Cost')} : ${totalCost}`;
|
||||
// 创建空 div 元素
|
||||
const emptyDiv = document.createElement('div');
|
||||
// 添加子元素到外层 div 元素中
|
||||
|
||||
@@ -154,7 +154,7 @@ export default function CostChart({ data }: { data: BillingData['status']['deduc
|
||||
position: 'center',
|
||||
show: true,
|
||||
formatter: function (params: any) {
|
||||
return '¥' + amount.toFixed(2) + `\n${t('Expenditure')}`;
|
||||
return amount.toFixed(2) + `\n${t('Expenditure')}`;
|
||||
},
|
||||
fontSize: 16,
|
||||
textStyle: {
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
import request from '@/service/request';
|
||||
import useSessionStore from '@/stores/session';
|
||||
import { displayMoney, formatMoney } from '@/utils/format';
|
||||
import { Box, Button, Flex, Image, Stack, Text } from '@chakra-ui/react';
|
||||
import { QueryClient, useIsFetching, useQuery } from '@tanstack/react-query';
|
||||
import { Box, Button, Flex, Image, Img, Stack, Text } from '@chakra-ui/react';
|
||||
import { QueryClient, useQuery } from '@tanstack/react-query';
|
||||
import styles from './user.module.scss';
|
||||
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { memo, useContext, useMemo, useRef } from 'react';
|
||||
import { memo, useMemo, useRef } from 'react';
|
||||
import { ApiResp } from '@/types/api';
|
||||
import jsyaml from 'js-yaml';
|
||||
import RechargeModal from './RechargeModal';
|
||||
import TransferModal from './TransferModal';
|
||||
import useEnvStore from '@/stores/env';
|
||||
|
||||
import shellCoin from '@/assert/shell_coin.svg';
|
||||
export default memo(function UserCard() {
|
||||
const getSession = useSessionStore((state) => state.getSession);
|
||||
const transferEnabled = useEnvStore((state) => state.transferEnabled);
|
||||
@@ -45,6 +45,7 @@ export default memo(function UserCard() {
|
||||
real_balance -= balance_raw?.data.deductionBalance;
|
||||
}
|
||||
const balance = real_balance;
|
||||
const stripePromise = useEnvStore((s) => s.stripePromise);
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
@@ -74,9 +75,10 @@ export default memo(function UserCard() {
|
||||
<Box fontSize="12px" fontWeight="400" alignSelf={'center'} mt="6px !important">
|
||||
{t('Balance')}
|
||||
</Box>
|
||||
<Box fontSize="24px" fontWeight="500" alignSelf={'center'} mt="3px !important">
|
||||
¥ {displayMoney(formatMoney(balance))}
|
||||
</Box>
|
||||
<Flex fontSize="24px" fontWeight="500" alignSelf={'center'} mt="3px !important">
|
||||
<Img src={shellCoin.src} w="16px" h="16px" mixBlendMode={'exclusion'} />
|
||||
<Text>{displayMoney(formatMoney(balance))}</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" alignSelf={'center'} gap="10px" mt={'20px !important'}>
|
||||
{transferEnabled && (
|
||||
<Button
|
||||
@@ -114,6 +116,8 @@ export default memo(function UserCard() {
|
||||
<RechargeModal
|
||||
ref={rechargeRef}
|
||||
balance={balance}
|
||||
stripePromise={stripePromise}
|
||||
request={request}
|
||||
onPaySuccess={() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['billing'], exact: false });
|
||||
refetch();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import useBillingStore from '@/stores/billing';
|
||||
import useEnvStore from '@/stores/env';
|
||||
import { displayMoney } from '@/utils/format';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Flex, Img } from '@chakra-ui/react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import shellcoin from '@/assert/shell_coin.svg';
|
||||
export default function PredictCard() {
|
||||
const { t } = useTranslation();
|
||||
const total = t('total');
|
||||
@@ -45,7 +45,8 @@ export default function PredictCard() {
|
||||
{item.name}
|
||||
</Flex>
|
||||
<Flex justify={'center'} align={'center'} h={'68px'}>
|
||||
¥{item.cost}
|
||||
<Img src={shellcoin.src} h="16px" w="16px" mr="4px" />
|
||||
{item.cost}
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
@@ -6,6 +6,7 @@ export type PaymentForm = {
|
||||
namespace: string;
|
||||
userId: string;
|
||||
amount: string;
|
||||
paymentMethod: 'wechat' | 'stripe';
|
||||
};
|
||||
|
||||
export const paymentMeta: CRDMeta = {
|
||||
@@ -31,7 +32,7 @@ export const generatePaymentCrd = (form: PaymentForm) => {
|
||||
spec: {
|
||||
userID: form.userId,
|
||||
amount: form.amount,
|
||||
paymentMethod: 'wechat'
|
||||
paymentMethod: form.paymentMethod
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import { EnvData } from '@/types/env';
|
||||
import { ApiResp } from '@/types/api';
|
||||
import useEnvStore from '@/stores/env';
|
||||
|
||||
// Make sure to call `loadStripe` outside of a component’s render to avoid
|
||||
// recreating the `Stripe` object on every render.
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
@@ -74,6 +76,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
||||
state.setTransferEnabled(!!data?.transferEnabled);
|
||||
state.setRechargeEnabled(!!data?.rechargeEnabled);
|
||||
state.setGpuEnabled(!!data?.gpuEnabled);
|
||||
state.setStripe(data?.stripePub || '');
|
||||
} catch (error) {
|
||||
console.error('get env error');
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse
|
||||
if (req.method !== 'POST') {
|
||||
return jsonRes(resp, { code: 405 });
|
||||
}
|
||||
const { amount } = req.body;
|
||||
const { amount, paymentMethod } = req.body;
|
||||
const kc = await authSession(req.headers);
|
||||
|
||||
if (!kc || amount <= 0) {
|
||||
@@ -36,7 +36,8 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse
|
||||
namespace,
|
||||
paymentName,
|
||||
userId: kubeUser.name,
|
||||
amount
|
||||
amount,
|
||||
paymentMethod
|
||||
};
|
||||
|
||||
const paymentCrd = generatePaymentCrd(form);
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse
|
||||
status: object;
|
||||
};
|
||||
};
|
||||
|
||||
console.log(paymentDesc);
|
||||
if (paymentDesc?.body?.status) {
|
||||
const paymentStatusResp = paymentDesc.body.status as paymentStatus;
|
||||
return jsonRes(resp, { data: paymentStatusResp });
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { generatePaymentCrd, PaymentForm } from '@/constants/payment';
|
||||
import { authSession } from '@/service/backend/auth';
|
||||
import { ApplyYaml, GetUserDefaultNameSpace } from '@/service/backend/kubernetes';
|
||||
import { jsonRes } from '@/service/backend/response';
|
||||
import { enableRecharge } from '@/service/enabled';
|
||||
import crypto from 'crypto';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(req: NextApiRequest, resp: NextApiResponse) {
|
||||
try {
|
||||
if (!enableRecharge()) {
|
||||
throw new Error('recharge is not enabled');
|
||||
}
|
||||
if (req.method !== 'POST') {
|
||||
return jsonRes(resp, { code: 405 });
|
||||
}
|
||||
const { amount } = req.body;
|
||||
const kc = await authSession(req.headers);
|
||||
|
||||
if (!kc || amount <= 0) {
|
||||
return jsonRes(resp, {
|
||||
code: 400,
|
||||
message: 'Amount cannot be less than 0'
|
||||
});
|
||||
}
|
||||
|
||||
const kubeUser = kc.getCurrentUser();
|
||||
if (kubeUser === null) {
|
||||
return jsonRes(resp, { code: 401, message: 'user not found' });
|
||||
}
|
||||
|
||||
// do payment
|
||||
const paymentName = crypto.randomUUID();
|
||||
const namespace = GetUserDefaultNameSpace(kubeUser.name);
|
||||
const form: PaymentForm = {
|
||||
namespace,
|
||||
paymentName,
|
||||
userId: kubeUser.name,
|
||||
amount,
|
||||
paymentMethod: 'stripe'
|
||||
};
|
||||
|
||||
const paymentCrd = generatePaymentCrd(form);
|
||||
const res = await ApplyYaml(kc, paymentCrd);
|
||||
console.log(res);
|
||||
return jsonRes(resp, {
|
||||
data: {
|
||||
paymentName: paymentName,
|
||||
extra: res[0]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
jsonRes(resp, { code: 500, message: 'pay error' });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generatePaymentCrd, generateTransferCrd, PaymentForm } from '@/constants/payment';
|
||||
import { generatePaymentCrd, generateTransferCrd } from '@/constants/payment';
|
||||
import { authSession } from '@/service/backend/auth';
|
||||
import {
|
||||
ApplyYaml,
|
||||
|
||||
@@ -10,7 +10,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
rechargeEnabled: enableRecharge(),
|
||||
transferEnabled: enableTransfer(),
|
||||
invoiceEnabled: enableInvoice(),
|
||||
gpuEnabled: enabelGpu()
|
||||
gpuEnabled: enabelGpu(),
|
||||
stripePub: process.env['STRIPE_PUB'] || ''
|
||||
},
|
||||
code: 200
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import OuterLink from '@/components/outerLink';
|
||||
import NotFound from '@/components/notFound';
|
||||
import PredictCard from '@/components/valuation/predictCard';
|
||||
import useEnvStore from '@/stores/env';
|
||||
import shellcoin from '@/assert/shell_coin.svg';
|
||||
type CardItem = {
|
||||
title: string;
|
||||
price: number[];
|
||||
@@ -110,7 +111,8 @@ function Valuation() {
|
||||
justifyContent="center"
|
||||
alignContent={'center'}
|
||||
>
|
||||
¥{item.price[0]}
|
||||
<Img src={shellcoin.src} h="28px" w="28px" my="auto" mr="4px" />
|
||||
<Text>{item.price[0]}</Text>
|
||||
</Heading>
|
||||
<Text ml="4px">
|
||||
{item.unit} / {t('Hour')}
|
||||
@@ -125,7 +127,10 @@ function Valuation() {
|
||||
py={'8px'}
|
||||
>
|
||||
<Box>{item.price[idx + 1]}</Box>
|
||||
<Box>{`¥${item.unit} / ${t(_item)}`}</Box>
|
||||
<Flex align={'center'}>
|
||||
<Img src={shellcoin.src} h="16px" w="16px" mr={'4px'} />
|
||||
<Text>{`${item.unit} / ${t(_item)}`}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
@@ -149,13 +154,19 @@ function Valuation() {
|
||||
pt="12px"
|
||||
px="24px"
|
||||
>
|
||||
<Box>{`¥${item.price}`}</Box>
|
||||
<Flex>
|
||||
<Img src={shellcoin.src} h="16px" w="16px" />
|
||||
<Text>{`${item.price}`}</Text>
|
||||
</Flex>
|
||||
<Stack align={'flex-end'} gap="0" fontSize={'10px'} fontWeight={'500'}>
|
||||
<Flex>
|
||||
<Img src={nvidaIcon.src} w="14px" h="14px" mr="6px" />
|
||||
<Text minW={'max-content'}>{item.name}</Text>
|
||||
</Flex>
|
||||
<Text>{`${gpuProps.unit} / ${t('Hour')}`}</Text>
|
||||
<Flex>
|
||||
<Img src={shellcoin.src} h="16px" w="16px" />
|
||||
<Text>{`${gpuProps.unit} / ${t('Hour')}`}</Text>
|
||||
</Flex>
|
||||
</Stack>
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import { Stripe, loadStripe } from '@stripe/stripe-js';
|
||||
import { create } from 'zustand';
|
||||
type EnvState = {
|
||||
rechargeEnabled: boolean;
|
||||
transferEnabled: boolean;
|
||||
invoiceEnabled: boolean;
|
||||
gpuEnabled: boolean;
|
||||
stripePromise: ReturnType<typeof loadStripe>;
|
||||
setRechargeEnabled: (enabled: boolean) => void;
|
||||
setTransferEnabled: (enabled: boolean) => void;
|
||||
setInvoiceEnabled: (enabled: boolean) => void;
|
||||
setGpuEnabled: (enabled: boolean) => void;
|
||||
setStripe: (pub: string) => void;
|
||||
};
|
||||
const useEnvStore = create<EnvState>((set, get) => ({
|
||||
rechargeEnabled: false,
|
||||
transferEnabled: false,
|
||||
invoiceEnabled: false,
|
||||
gpuEnabled: false,
|
||||
stripePromise: Promise.resolve(null),
|
||||
setRechargeEnabled: (enabled: boolean) => set({ rechargeEnabled: enabled }),
|
||||
setTransferEnabled: (enabled: boolean) => set({ transferEnabled: enabled }),
|
||||
setInvoiceEnabled: (enabled: boolean) => set({ invoiceEnabled: enabled }),
|
||||
setGpuEnabled(enabled) {
|
||||
set({ gpuEnabled: enabled });
|
||||
}
|
||||
},
|
||||
setStripe: (pub: string) => set({ stripePromise: loadStripe(pub) })
|
||||
}));
|
||||
|
||||
export default useEnvStore;
|
||||
|
||||
@@ -3,4 +3,5 @@ export type EnvData = {
|
||||
transferEnabled: boolean;
|
||||
invoiceEnabled: boolean;
|
||||
gpuEnabled: boolean;
|
||||
stripePub: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": 0
|
||||
}
|
||||
}
|
||||
{
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/public/trainData/
|
||||
/.vscode/
|
||||
platform.json
|
||||
.yalc/
|
||||
yalc.lock
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
/public/trainData/
|
||||
/.vscode/
|
||||
platform.json
|
||||
.yalc/
|
||||
yalc.lock
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
dist
|
||||
.vscode
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
.vscode
|
||||
**/.DS_Store
|
||||
node_modules
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
module.exports = {
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
quoteProps: 'as-needed',
|
||||
jsxSingleQuote: false,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
jsxBracketSameLine: false,
|
||||
arrowParens: 'always',
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
requirePragma: false,
|
||||
insertPragma: false,
|
||||
proseWrap: 'preserve',
|
||||
htmlWhitespaceSensitivity: 'css',
|
||||
endOfLine: 'lf'
|
||||
};
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
# sealos app launchpad
|
||||
|
||||
## project tree
|
||||
|
||||
```bash
|
||||
.
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── deploy
|
||||
│ └── manifests
|
||||
│ └── frontend.yaml
|
||||
├── next-env.d.ts
|
||||
├── next.config.js
|
||||
├── package.json
|
||||
├── pnpm-lock.yaml
|
||||
├── public
|
||||
│ └── favicon.ico
|
||||
├── src
|
||||
│ ├── api # FE api
|
||||
│ ├── components # global components
|
||||
│ │ ├── DBStatusTag
|
||||
│ │ ├── ButtonGroup
|
||||
│ │ ├── FormControl
|
||||
│ │ ├── Icon
|
||||
│ │ │ ├── icons # svg icon
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PodLineChart
|
||||
│ │ ├── RangeInput
|
||||
│ │ ├── RangeSlider
|
||||
│ │ ├── Slider
|
||||
│ │ └── YamlCode
|
||||
│ ├── constants # global constant data
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── editApp.ts
|
||||
│ │ └── theme.ts
|
||||
│ ├── hooks # global hooks
|
||||
│ │ ├── useConfirm.tsx
|
||||
│ │ ├── useLoading.tsx
|
||||
│ │ ├── useScreen.ts
|
||||
│ │ └── useToast.ts
|
||||
│ ├── mock
|
||||
│ ├── pages
|
||||
│ │ ├── 404.tsx
|
||||
│ │ ├── _app.tsx
|
||||
│ │ ├── _document.tsx
|
||||
│ │ ├── api # server api
|
||||
│ │ ├── app
|
||||
│ │ │ ├── detail
|
||||
│ │ │ └── edit
|
||||
│ │ └── apps
|
||||
│ │ └── index.tsx
|
||||
│ ├── services # server function
|
||||
│ │ ├── backend
|
||||
│ │ │ ├── auth.ts
|
||||
│ │ │ ├── kubernetes.ts
|
||||
│ │ │ └── response.ts
|
||||
│ │ ├── error.ts
|
||||
│ │ ├── kubernet.ts
|
||||
│ │ └── request.ts
|
||||
│ ├── store # FE store
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── global.ts
|
||||
│ │ └── static.ts
|
||||
│ ├── styles
|
||||
│ │ └── reset.scss
|
||||
│ ├── types
|
||||
│ │ ├── app.d.ts
|
||||
│ │ ├── index.d.ts
|
||||
│ │ └── user.d.ts
|
||||
│ └── utils
|
||||
│ ├── adapt.ts # format api data
|
||||
│ ├── deployYaml2Json.ts # form data to yaml
|
||||
│ ├── tools.ts
|
||||
│ └── user.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
# sealos app launchpad
|
||||
|
||||
## project tree
|
||||
|
||||
```bash
|
||||
.
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
├── README.md
|
||||
├── deploy
|
||||
│ └── manifests
|
||||
│ └── frontend.yaml
|
||||
├── next-env.d.ts
|
||||
├── next.config.js
|
||||
├── package.json
|
||||
├── pnpm-lock.yaml
|
||||
├── public
|
||||
│ └── favicon.ico
|
||||
├── src
|
||||
│ ├── api # FE api
|
||||
│ ├── components # global components
|
||||
│ │ ├── DBStatusTag
|
||||
│ │ ├── ButtonGroup
|
||||
│ │ ├── FormControl
|
||||
│ │ ├── Icon
|
||||
│ │ │ ├── icons # svg icon
|
||||
│ │ │ └── index.tsx
|
||||
│ │ ├── PodLineChart
|
||||
│ │ ├── RangeInput
|
||||
│ │ ├── RangeSlider
|
||||
│ │ ├── Slider
|
||||
│ │ └── YamlCode
|
||||
│ ├── constants # global constant data
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── editApp.ts
|
||||
│ │ └── theme.ts
|
||||
│ ├── hooks # global hooks
|
||||
│ │ ├── useConfirm.tsx
|
||||
│ │ ├── useLoading.tsx
|
||||
│ │ ├── useScreen.ts
|
||||
│ │ └── useToast.ts
|
||||
│ ├── mock
|
||||
│ ├── pages
|
||||
│ │ ├── 404.tsx
|
||||
│ │ ├── _app.tsx
|
||||
│ │ ├── _document.tsx
|
||||
│ │ ├── api # server api
|
||||
│ │ ├── app
|
||||
│ │ │ ├── detail
|
||||
│ │ │ └── edit
|
||||
│ │ └── apps
|
||||
│ │ └── index.tsx
|
||||
│ ├── services # server function
|
||||
│ │ ├── backend
|
||||
│ │ │ ├── auth.ts
|
||||
│ │ │ ├── kubernetes.ts
|
||||
│ │ │ └── response.ts
|
||||
│ │ ├── error.ts
|
||||
│ │ ├── kubernet.ts
|
||||
│ │ └── request.ts
|
||||
│ ├── store # FE store
|
||||
│ │ ├── app.ts
|
||||
│ │ ├── global.ts
|
||||
│ │ └── static.ts
|
||||
│ ├── styles
|
||||
│ │ └── reset.scss
|
||||
│ ├── types
|
||||
│ │ ├── app.d.ts
|
||||
│ │ ├── index.d.ts
|
||||
│ │ └── user.d.ts
|
||||
│ └── utils
|
||||
│ ├── adapt.ts # format api data
|
||||
│ ├── deployYaml2Json.ts # form data to yaml
|
||||
│ ├── tools.ts
|
||||
│ └── user.ts
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user