init
This commit is contained in:
commit
60ea55d989
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
38
README.md
Normal file
38
README.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# create-svelte
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npm create svelte@latest
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npm create svelte@latest my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
|
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
3453
package-lock.json
generated
Normal file
3453
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "svelte-platform-admin-valera",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@fontsource/fira-mono": "^4.5.10",
|
||||||
|
"@neoconfetti/svelte": "^1.0.0",
|
||||||
|
"@sveltejs/adapter-auto": "^3.0.0",
|
||||||
|
"@sveltejs/kit": "^2.0.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||||
|
"@zerodevx/svelte-toast": "^0.9.5",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"daisyui": "^4.12.10",
|
||||||
|
"postcss": "^8.4.39",
|
||||||
|
"svelte": "^4.2.7",
|
||||||
|
"svelte-check": "^3.6.0",
|
||||||
|
"tailwindcss": "^3.4.4",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^5.0.3"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.2",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
|
"serialize-error": "^11.0.3"
|
||||||
|
}
|
||||||
|
}
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
3
src/app.css
Normal file
3
src/app.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
12
src/app.html
Normal file
12
src/app.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
41
src/lib/auth/Auth.js
Normal file
41
src/lib/auth/Auth.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { makeGet } from "$lib/tools/requests/requests";
|
||||||
|
import { isPast, parseISO } from "date-fns";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
|
const LocalStorageItemName =
|
||||||
|
"99e546e033b0de65338b3410d5d779bfae88d513dd1238190b26a509";
|
||||||
|
|
||||||
|
export function saveAuthInfo(jwt) {
|
||||||
|
localStorage.setItem(LocalStorageItemName, JSON.stringify({ a: jwt }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthInfo() {
|
||||||
|
return localStorage.getItem(LocalStorageItemName)
|
||||||
|
? JSON.parse(localStorage.getItem(LocalStorageItemName))
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAuth() {
|
||||||
|
let item = getAuthInfo();
|
||||||
|
if (!item) return false;
|
||||||
|
if (!item.a) return false;
|
||||||
|
const result = await makeGet("rate", makeAuthHeaderForAxios(item.a));
|
||||||
|
if (result.error) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkAuthSync() {
|
||||||
|
let result = getAuthInfo();
|
||||||
|
if (!result) return false;
|
||||||
|
let decoded = jwtDecode(result.a);
|
||||||
|
return !(isPast(parseISO(new Date(decoded.exp*1000).toISOString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeAuthHeaderForAxios = (jwt) => {
|
||||||
|
return {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + jwt,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
47
src/lib/tools/requests/Requests.js
Normal file
47
src/lib/tools/requests/Requests.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { serializeError } from "serialize-error";
|
||||||
|
|
||||||
|
|
||||||
|
function defaultResponseProcessor(response, dataProcessor) {
|
||||||
|
return {error: false, data: dataProcessor(response.data), status: response.status};
|
||||||
|
}
|
||||||
|
function defaultErrorProcessor(error) {
|
||||||
|
return {error: true, data: serializeError(error), status: (error.code === "ERR_NETWORK" ? 502:serializeError(error).status)};
|
||||||
|
}
|
||||||
|
function defaultDataResponseProcessor(data) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let BasicURLValue = "https://test.0x000f.ru/api/v1/";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async function makePost(url, data, options = undefined,
|
||||||
|
responseProcessor = defaultResponseProcessor,
|
||||||
|
dataResponseProcessor = defaultDataResponseProcessor,
|
||||||
|
errorProcessor = defaultErrorProcessor)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const res = await axios.post(BasicURLValue + url, data, options);
|
||||||
|
return responseProcessor(res, dataResponseProcessor);
|
||||||
|
} catch (error) {
|
||||||
|
return errorProcessor(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeGet(url, options = undefined,
|
||||||
|
responseProcessor = defaultResponseProcessor,
|
||||||
|
dataResponseProcessor = defaultDataResponseProcessor,
|
||||||
|
errorProcessor = defaultErrorProcessor)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const res = await axios.get(BasicURLValue + url, options);
|
||||||
|
return responseProcessor(res, dataResponseProcessor);
|
||||||
|
} catch (error) {
|
||||||
|
return errorProcessor(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {BasicURLValue, makePost, makeGet, defaultResponseProcessor, defaultDataResponseProcessor, defaultErrorProcessor};
|
16
src/lib/tools/state-storage/StateStorage.js
Normal file
16
src/lib/tools/state-storage/StateStorage.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
export function StateStorage(states) {
|
||||||
|
this.states = {...states};
|
||||||
|
this.listeners = [];
|
||||||
|
this.subscribe = (func) => {
|
||||||
|
this.listeners.push(func);
|
||||||
|
func({...this.states});
|
||||||
|
};
|
||||||
|
this.update = (changes) => {
|
||||||
|
changes(this.states);
|
||||||
|
for (const i of this.listeners) {
|
||||||
|
i({...this.states});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.get = ()=>{return{...this.states}};
|
||||||
|
}
|
12
src/lib/tools/strings/Strings.js
Normal file
12
src/lib/tools/strings/Strings.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
export function isStringEmptyOrSpaces(str) {
|
||||||
|
if (typeof str === "string") return str == null || str?.trim() === "";
|
||||||
|
else if (typeof str === "object") {
|
||||||
|
if (Array.isArray(str)) {
|
||||||
|
for (const i of str) {
|
||||||
|
if (i == null || i?.trim() === "") return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
28
src/lib/tools/toaster/Toaster.js
Normal file
28
src/lib/tools/toaster/Toaster.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { toast } from "@zerodevx/svelte-toast";
|
||||||
|
//#FFBE00
|
||||||
|
export function sayInfo(msg, duration = 2000) {
|
||||||
|
toast.push(msg, {
|
||||||
|
theme: {
|
||||||
|
"--toastBackground": "#00B5FF"
|
||||||
|
},
|
||||||
|
duration: duration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function sayWarning(msg, duration = 2000) {
|
||||||
|
toast.push(msg, {
|
||||||
|
theme: {
|
||||||
|
"--toastBackground": "#FFBE00",
|
||||||
|
"--toastColor": "#000000"
|
||||||
|
},
|
||||||
|
duration: duration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function sayError(msg, duration = 2000) {
|
||||||
|
toast.push(msg, {
|
||||||
|
theme: {
|
||||||
|
"--toastBackground": "#FF5861",
|
||||||
|
"--toastColor": "#000000"
|
||||||
|
},
|
||||||
|
duration: duration
|
||||||
|
});
|
||||||
|
}
|
13
src/lib/tools/url/URLTools.js
Normal file
13
src/lib/tools/url/URLTools.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
export function getCurrentPath() {
|
||||||
|
return window.location.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redirect(url) {
|
||||||
|
var link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.style.display = 'none';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
56
src/lib/ui-components/navbar.svelte
Normal file
56
src/lib/ui-components/navbar.svelte
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<script>
|
||||||
|
|
||||||
|
import { getAuthInfo } from "$lib/auth/Auth";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div class="navbar bg-base-100">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<div class="dropdown">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M4 6h16M4 12h8m-8 6h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a href="/userslist">Пользователи</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/newuser">Добавить пользователя</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/disputes">Споры</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<a href="/" class="btn btn-ghost text-xl">HostaPay</a>
|
||||||
|
<p class="text-sm ml-1 mt-2">{jwtDecode(getAuthInfo()?.a)?.role == "4" ? "Admin":"Moder"}</p>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-center hidden lg:flex">
|
||||||
|
<ul class="menu menu-horizontal px-1">
|
||||||
|
<li>
|
||||||
|
<a href="/userslist">Пользователи</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/newuser">Добавить пользователя</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="/disputes">Споры</a></li>
|
||||||
|
<!-- <li><a>Item 3</a></li> -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<button class="btn btn-error btn-outline">Выход</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
61
src/routes/+layout.svelte
Normal file
61
src/routes/+layout.svelte
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<script>
|
||||||
|
import { SvelteToast } from "@zerodevx/svelte-toast";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import "../app.css";
|
||||||
|
import Navbar from "../lib/ui-components/navbar.svelte";
|
||||||
|
import { checkAuth, checkAuthSync } from "../lib/auth/Auth";
|
||||||
|
|
||||||
|
let loggedIn = false;
|
||||||
|
let authCheckInProgress = false;
|
||||||
|
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
|
||||||
|
if (!checkAuthSync() && window.location.pathname !== "/login") {
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
else if (checkAuthSync())
|
||||||
|
{
|
||||||
|
loggedIn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loggedIn}
|
||||||
|
<div class="fixed left-0 right-0 top-0 flex">
|
||||||
|
<Navbar />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex justify-center flex-shrink-0 w-full h-[calc(100vh-52px-68px)] mt-[68px] overflow-auto p-4">
|
||||||
|
<div class="flex w-full md:max-w-[90%] lg:max-w-[1080px]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer footer-center bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>
|
||||||
|
Copyright © {new Date().getFullYear()} - All right reserved
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<SvelteToast
|
||||||
|
options={{
|
||||||
|
duration: 2000, // duration of progress bar tween to the `next` value
|
||||||
|
initial: 1, // initial progress bar value
|
||||||
|
next: 0, // next progress value
|
||||||
|
pausable: false, // pause progress bar tween on mouse hover
|
||||||
|
dismissable: true, // allow dismiss with close button
|
||||||
|
reversed: false, // insert new toast to bottom of stack
|
||||||
|
intro: { x: 256 }, // toast intro fly animation settings
|
||||||
|
theme: {
|
||||||
|
// "--toastColor": "var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)))",
|
||||||
|
// "--toastBackground": "var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)))",
|
||||||
|
// "--toastBarBackground": "#2F855A",
|
||||||
|
}, // css var overrides
|
||||||
|
classes: [], // user-defined classes
|
||||||
|
}}
|
||||||
|
/>
|
0
src/routes/+page.svelte
Normal file
0
src/routes/+page.svelte
Normal file
1
src/routes/home/+page.svelte
Normal file
1
src/routes/home/+page.svelte
Normal file
@ -0,0 +1 @@
|
|||||||
|
<h1 class="text-xl font-bold">BOO1</h1>
|
100
src/routes/login/+page.svelte
Normal file
100
src/routes/login/+page.svelte
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<script>
|
||||||
|
import { makePost } from "$lib/tools/requests/Requests";
|
||||||
|
import { sayError, sayInfo, sayWarning } from "$lib/tools/toaster/Toaster";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import {checkAuth, saveAuthInfo} from "../../lib/auth/Auth";
|
||||||
|
|
||||||
|
//6da8a96a-7253-45e2-a3c1-e00d5ecdc65d - valid
|
||||||
|
let tokenValid = true;
|
||||||
|
let totpValid = true;
|
||||||
|
let showLoadingSpinner = false;
|
||||||
|
|
||||||
|
let input_token = null;
|
||||||
|
let input_totp = null;
|
||||||
|
async function sendLogin() {
|
||||||
|
if (input_token.value === "" || input_totp.value === "") {
|
||||||
|
sayError("Заполните все поля!");
|
||||||
|
tokenValid = input_token.value !== "";
|
||||||
|
totpValid = input_totp.value !== "";
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
tokenValid = true;
|
||||||
|
totpValid = true;
|
||||||
|
showLoadingSpinner = true;
|
||||||
|
const result = await makePost("client/login", {
|
||||||
|
"user_token": input_token.value,
|
||||||
|
"totp": input_totp.value
|
||||||
|
});
|
||||||
|
if(result.error)
|
||||||
|
{
|
||||||
|
sayError("Ошибка входа!");
|
||||||
|
tokenValid = false;
|
||||||
|
totpValid = false;
|
||||||
|
showLoadingSpinner = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
saveAuthInfo(result.data.token);
|
||||||
|
window.location.href = "/";
|
||||||
|
showLoadingSpinner = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let checkAuthInProgress = true;
|
||||||
|
// onMount(async () => {
|
||||||
|
// let result = await checkAuth();
|
||||||
|
// if (result && window.location.pathname === "/login") {
|
||||||
|
// window.location.href = "/";
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// checkAuthInProgress = false;
|
||||||
|
// });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full h-full flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
class="bg-base-300 w-full h-full max-w-[500px] max-h-[300px] flex flex-col justify-center gap-2 p-4 rounded-box"
|
||||||
|
>
|
||||||
|
<h1 class="text-4xl self-center mb-4">Вход</h1>
|
||||||
|
<label
|
||||||
|
class={"input input-bordered flex items-center gap-2 " +
|
||||||
|
(tokenValid ? "input-warning" : "input-error")}
|
||||||
|
>
|
||||||
|
Токен:
|
||||||
|
<input
|
||||||
|
bind:this={input_token}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class={"input input-bordered flex items-center gap-2 " +
|
||||||
|
(totpValid ? "input-warning" : "input-error")}
|
||||||
|
>
|
||||||
|
2FA Код:
|
||||||
|
<input
|
||||||
|
bind:this={input_totp}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<!-- <button class="btn-link btn max-w-[140px] self-center">У меня нет 2FA</button> -->
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
sendLogin();
|
||||||
|
}}
|
||||||
|
class="btn btn-primary text-lg">
|
||||||
|
Войти
|
||||||
|
{#if showLoadingSpinner}
|
||||||
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-black" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
193
src/routes/newuser/+page.svelte
Normal file
193
src/routes/newuser/+page.svelte
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<script>
|
||||||
|
import { isStringEmptyOrSpaces } from "../../lib/tools/strings/Strings";
|
||||||
|
import { makePost, makeGet } from "$lib/tools/requests/Requests";
|
||||||
|
import {
|
||||||
|
checkAuthSync,
|
||||||
|
getAuthInfo,
|
||||||
|
makeAuthHeaderForAxios,
|
||||||
|
} from "$lib/auth/Auth";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster";
|
||||||
|
|
||||||
|
let value_name = "";
|
||||||
|
let value_surname = "";
|
||||||
|
let value_balance = 0;
|
||||||
|
let value_role = "-1";
|
||||||
|
let value_bid = 0;
|
||||||
|
let value_trading = false;
|
||||||
|
|
||||||
|
$: checkFields(
|
||||||
|
value_name,
|
||||||
|
value_surname,
|
||||||
|
value_balance,
|
||||||
|
value_role,
|
||||||
|
value_bid
|
||||||
|
);
|
||||||
|
|
||||||
|
function checkFields(name, surname, balance, role, bid) {
|
||||||
|
if (isStringEmptyOrSpaces(name) || name.length < 3) {
|
||||||
|
canCreateUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((bid !== 0 && !bid) || bid < 0) {
|
||||||
|
canCreateUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isStringEmptyOrSpaces(surname) || surname.length < 3) {
|
||||||
|
canCreateUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (role === "-1") {
|
||||||
|
canCreateUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((balance !== 0 && !balance) || balance < 0) {
|
||||||
|
canCreateUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canCreateUser = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let canCreateUser = false;
|
||||||
|
let loadingUserToken = false;
|
||||||
|
let userToken = "";
|
||||||
|
|
||||||
|
let temp_userToken = "";
|
||||||
|
|
||||||
|
async function createUser() {
|
||||||
|
loadingUserToken = true;
|
||||||
|
const result = await makeGet(
|
||||||
|
"createUser",
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (result.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.error) {
|
||||||
|
sayError("Не удалось создать пользователя");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
temp_userToken = result.data.token;
|
||||||
|
setupNewUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupNewUser() {
|
||||||
|
//todo
|
||||||
|
const result = await makePost(
|
||||||
|
"admin/changeUser",
|
||||||
|
{
|
||||||
|
token: temp_userToken,
|
||||||
|
name: value_name,
|
||||||
|
surname: value_surname,
|
||||||
|
balance: value_balance.toString(),
|
||||||
|
rate: "0",
|
||||||
|
can_trade: value_trading ? "true":"false",
|
||||||
|
can_trade_global: value_trading ? "true":"false",
|
||||||
|
role: value_role,
|
||||||
|
bid: value_bid.toString(),
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if(result.status === 401)
|
||||||
|
{
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(result.error)
|
||||||
|
{
|
||||||
|
sayError("Не удалось настроить пользователя");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userToken = temp_userToken;
|
||||||
|
loadingUserToken = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-8">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Создание нового пользователя</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col gap-4 p-4 rounded-box bg-base-300">
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Имя:
|
||||||
|
<input
|
||||||
|
bind:value={value_name}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Фамилия:
|
||||||
|
<input
|
||||||
|
bind:value={value_surname}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Баланс (USDT):
|
||||||
|
<input
|
||||||
|
bind:value={value_balance}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Ставка (%):
|
||||||
|
<input
|
||||||
|
bind:value={value_bid}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
bind:value={value_role}
|
||||||
|
class="select select-bordered w-full text-base"
|
||||||
|
>
|
||||||
|
<option disabled selected value="-1">Права доступа</option>
|
||||||
|
<option value="1">Пользователь</option>
|
||||||
|
<option value="2">Модератор</option>
|
||||||
|
<option value="4">Администратор</option>
|
||||||
|
</select>
|
||||||
|
<div class="flex p-4 items-center gap-2">
|
||||||
|
<p>Торговля:</p>
|
||||||
|
<input
|
||||||
|
bind:checked={value_trading}
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
on:click={() => {
|
||||||
|
createUser();
|
||||||
|
}}
|
||||||
|
class={"btn text-base " +
|
||||||
|
(loadingUserToken ? "btn-warning " : "btn-primary ") +
|
||||||
|
(userToken !== "" ? "disabled:text-info" : "disabled:text-warning") +
|
||||||
|
(canCreateUser ? "" : "disabled:text-opacity-50")}
|
||||||
|
disabled={!canCreateUser || loadingUserToken || userToken !== ""}
|
||||||
|
>
|
||||||
|
{#if loadingUserToken}
|
||||||
|
{"Ожидайте токен"}
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
{:else if userToken === ""}
|
||||||
|
{"Создать пользователя"}
|
||||||
|
{:else if canCreateUser}
|
||||||
|
{"Пользователь создан"}
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{#if userToken !== ""}
|
||||||
|
<div class="w-full flex p-4">
|
||||||
|
<p class="text-xl">
|
||||||
|
Токен пользователя: <span class="text-info text-xl">{userToken}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
33
src/routes/user/change/[userToken]/+page.js
Normal file
33
src/routes/user/change/[userToken]/+page.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { browser } from "$app/environment";
|
||||||
|
import { getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth";
|
||||||
|
import { makePost } from "$lib/tools/requests/requests";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster";
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
|
if(!browser) return;
|
||||||
|
const { userToken } = params;
|
||||||
|
let userResult = await makePost(
|
||||||
|
"getUser",
|
||||||
|
{
|
||||||
|
token: userToken,
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (userResult.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (userResult.error) {
|
||||||
|
sayError("Не удалось настроить пользователя");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
userResult["can_trade"] = userResult["can_trade"] === "t" ? true:false;
|
||||||
|
userResult["can_trade_global"] = userResult["can_trade_global"] === "t" ? true:false;
|
||||||
|
userResult["balance"] = Number(userResult["balance"]);
|
||||||
|
userResult["bid"] = Number(userResult["bid"]);
|
||||||
|
return {
|
||||||
|
token: userToken,
|
||||||
|
userData: userResult.data
|
||||||
|
};
|
||||||
|
}
|
151
src/routes/user/change/[userToken]/+page.svelte
Normal file
151
src/routes/user/change/[userToken]/+page.svelte
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
<script>
|
||||||
|
import { getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth.js";
|
||||||
|
import { makePost } from "$lib/tools/requests/requests.js";
|
||||||
|
import { isStringEmptyOrSpaces } from "$lib/tools/strings/Strings";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster.js";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
let { userData } = data;
|
||||||
|
let { token } = data;
|
||||||
|
let value_role = jwtDecode(getAuthInfo()?.a).role.toString();
|
||||||
|
|
||||||
|
let changingUser = false;
|
||||||
|
let canChangeUser = true;
|
||||||
|
|
||||||
|
$: checkFields(
|
||||||
|
userData.name,
|
||||||
|
userData.surname,
|
||||||
|
userData.balance,
|
||||||
|
value_role,
|
||||||
|
userData.bid
|
||||||
|
);
|
||||||
|
|
||||||
|
function checkFields(name, surname, balance, role, bid) {
|
||||||
|
// balance = Number(balance);
|
||||||
|
// bid = Number(balance);
|
||||||
|
if (isStringEmptyOrSpaces(name) || name.length < 3) {
|
||||||
|
canChangeUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((bid !== 0 && !bid) || bid < 0) {
|
||||||
|
canChangeUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isStringEmptyOrSpaces(surname) || surname.length < 3) {
|
||||||
|
canChangeUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (role === "-1") {
|
||||||
|
canChangeUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((balance !== 0 && !balance) || balance < 0) {
|
||||||
|
canChangeUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canChangeUser = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changeUserInfo() {
|
||||||
|
changingUser = true;
|
||||||
|
const userResult = await makePost(
|
||||||
|
"admin/changeUser",
|
||||||
|
{
|
||||||
|
token: token,
|
||||||
|
name: userData.name,
|
||||||
|
surname: userData.surname,
|
||||||
|
balance: userData.balance.toString(),
|
||||||
|
rate: "0",
|
||||||
|
can_trade: userData["can_trade"] ? "true":"false",
|
||||||
|
can_trade_global: userData["can_trade_global"] ? "true":"false",
|
||||||
|
role: value_role,
|
||||||
|
bid: userData.bid.toString(),
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (userResult.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
changingUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userResult.error) {
|
||||||
|
sayError("Не удалось изменить пользователя");
|
||||||
|
changingUser = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("ref")?.click();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-8">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Изменение информации о пользователе</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300">
|
||||||
|
<div class="w-full flex flex-col gap-4 p-4 rounded-box bg-base-300">
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Имя:
|
||||||
|
<input
|
||||||
|
bind:value={userData.name}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Фамилия:
|
||||||
|
<input
|
||||||
|
bind:value={userData.surname}
|
||||||
|
type="text"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Баланс (USDT):
|
||||||
|
<input
|
||||||
|
bind:value={userData.balance}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class={"input input-bordered flex items-center gap-2 "}>
|
||||||
|
Ставка (%):
|
||||||
|
<input
|
||||||
|
bind:value={userData.bid}
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
class="grow text-lg text-info"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
bind:value={value_role}
|
||||||
|
class="select select-bordered w-full text-base"
|
||||||
|
>
|
||||||
|
<option disabled selected value="-1">Права доступа</option>
|
||||||
|
<option value="1">Пользователь</option>
|
||||||
|
<option value="2">Модератор</option>
|
||||||
|
<option value="4">Администратор</option>
|
||||||
|
</select>
|
||||||
|
<div class="flex p-4 items-center gap-2">
|
||||||
|
<p>Торговля:</p>
|
||||||
|
<input
|
||||||
|
bind:checked={userData["can_trade"]}
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button on:click={()=>{
|
||||||
|
changeUserInfo();
|
||||||
|
}} class="btn btn-primary text-base" disabled={changingUser || !canChangeUser}>
|
||||||
|
Сохранить
|
||||||
|
{#if changingUser}
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<a href={window.location.pathname.replace("change", "profile")} class="hidden" id="ref"></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
29
src/routes/user/profile/[userToken]/+page.js
Normal file
29
src/routes/user/profile/[userToken]/+page.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { browser } from "$app/environment";
|
||||||
|
import { getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth";
|
||||||
|
import { makePost } from "$lib/tools/requests/requests";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster";
|
||||||
|
|
||||||
|
export async function load({ params }) {
|
||||||
|
if(!browser) return;
|
||||||
|
const { userToken } = params;
|
||||||
|
const userResult = await makePost(
|
||||||
|
"getUser",
|
||||||
|
{
|
||||||
|
token: userToken,
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (userResult.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (userResult.error) {
|
||||||
|
sayError("Не удалось настроить пользователя");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
token: userToken,
|
||||||
|
userData: userResult.data
|
||||||
|
};
|
||||||
|
}
|
196
src/routes/user/profile/[userToken]/+page.svelte
Normal file
196
src/routes/user/profile/[userToken]/+page.svelte
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import { getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth.js";
|
||||||
|
import { makePost } from "$lib/tools/requests/requests.js";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster.js";
|
||||||
|
import { redirect } from "$lib/tools/url/URLTools.js";
|
||||||
|
|
||||||
|
export let data;
|
||||||
|
let { userData } = data;
|
||||||
|
let { token } = data;
|
||||||
|
|
||||||
|
let userInfoReloading = false;
|
||||||
|
|
||||||
|
async function deleteUser() {
|
||||||
|
const result = await makePost(
|
||||||
|
"deleteUser",
|
||||||
|
{
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (result.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.error) {
|
||||||
|
sayError("Не удалось удалить пользователя");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redirect("/userslist");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadUserInfo() {
|
||||||
|
userInfoReloading = true;
|
||||||
|
const userResult = await makePost(
|
||||||
|
"getUser",
|
||||||
|
{
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (userResult.status === 401) {
|
||||||
|
userInfoReloading = false;
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userResult.error) {
|
||||||
|
userInfoReloading = false;
|
||||||
|
sayError("Не удалось настроить пользователя");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userData = userResult.data;
|
||||||
|
userInfoReloading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let userOrders = [];
|
||||||
|
async function getUserOrders() {
|
||||||
|
const result = await makePost(
|
||||||
|
"getUserOrders",
|
||||||
|
{
|
||||||
|
token: token,
|
||||||
|
page: 0,
|
||||||
|
orderBy: "id",
|
||||||
|
statuses_to_send: 2,
|
||||||
|
},
|
||||||
|
makeAuthHeaderForAxios(getAuthInfo()?.a)
|
||||||
|
);
|
||||||
|
if (result.status === 401) {
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.error) {
|
||||||
|
sayError("Не удалось получить ордеры пользователя");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userOrders = result.data.data;
|
||||||
|
console.log(userOrders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
getUserOrders();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-8">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Информация о пользователе</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300">
|
||||||
|
<div class="flex gap-2 p-2 items-center text-lg">
|
||||||
|
<p class="font-bold">Токен:</p>
|
||||||
|
<p class="text-info">{token}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 p-2 items-center text-lg">
|
||||||
|
<p class="font-bold">Имя и фамилия:</p>
|
||||||
|
<p class="text-info">{userData.name} {userData.surname}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 p-2 items-center text-lg">
|
||||||
|
<p class="font-bold">Баланс:</p>
|
||||||
|
<p class="text-info">{userData.balance} USDT</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 p-2 items-center text-lg">
|
||||||
|
<p class="font-bold">Ставка:</p>
|
||||||
|
<p class="text-info">{userData.bid} %</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 p-2 items-center text-lg">
|
||||||
|
<p class="font-bold">Торговля:</p>
|
||||||
|
<p class={userData["can_trade"] === "t" ? "text-primary" : "text-error"}>
|
||||||
|
{userData["can_trade"] === "t" ? "Активна" : "Отключена"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex gap-4 mt-8">
|
||||||
|
<button
|
||||||
|
class="btn btn-neutral w-[100px]"
|
||||||
|
on:click={() => {
|
||||||
|
reloadUserInfo();
|
||||||
|
}}
|
||||||
|
disabled={userInfoReloading}
|
||||||
|
>
|
||||||
|
{#if !userInfoReloading}
|
||||||
|
{"Обновить"}
|
||||||
|
{:else}
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
href={window.location.pathname.replace("profile", "change")}
|
||||||
|
class="btn btn-info">Изменить</a
|
||||||
|
>
|
||||||
|
<button on:click={()=>{
|
||||||
|
deleteUser();
|
||||||
|
}} class="btn btn-error ml-auto">Удалить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Реквизиты пользователя</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300"></div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Девайсы пользователя</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300"></div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Текущие заявки</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table">
|
||||||
|
<!-- head -->
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Статус</th>
|
||||||
|
<th>Сумма</th>
|
||||||
|
<th>Курс</th>
|
||||||
|
<th>СБП</th>
|
||||||
|
<th>Мерчант</th>
|
||||||
|
<th>Время создания</th>
|
||||||
|
<th>Время закрытия</th>
|
||||||
|
<!-- <th></th> -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each userOrders as order}
|
||||||
|
<tr class="hover:bg-neutral group">
|
||||||
|
<td class="font-normal">{order["id"]}</td>
|
||||||
|
<td class="font-semibold">{order["status"]}</td>
|
||||||
|
<td>{order["summa"]} USDT</td>
|
||||||
|
<td>{order["rate"]} RUB</td>
|
||||||
|
<td>{order["is_sbp"] === "t" ? "да":"нет"}</td>
|
||||||
|
<td>{order["merchant_id"]}</td>
|
||||||
|
<td>{order["creationtime"]}</td>
|
||||||
|
<td>{order["closetime"]}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Заявки на пополнение</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300"></div>
|
||||||
|
</div>
|
71
src/routes/userslist/+page.svelte
Normal file
71
src/routes/userslist/+page.svelte
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import { checkAuthSync, getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth";
|
||||||
|
import { makeGet } from "$lib/tools/requests/requests";
|
||||||
|
import { sayError } from "$lib/tools/toaster/Toaster";
|
||||||
|
|
||||||
|
let users = [];
|
||||||
|
let usersReady = false;
|
||||||
|
|
||||||
|
async function getUsers() {
|
||||||
|
if(checkAuthSync())
|
||||||
|
{
|
||||||
|
const result = await makeGet("getUsers", makeAuthHeaderForAxios(getAuthInfo()?.a));
|
||||||
|
users = result.data;
|
||||||
|
usersReady = true;
|
||||||
|
// console.log(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sayError("Данные авторизации устарели");
|
||||||
|
window.location.href = "/";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(browser)
|
||||||
|
{
|
||||||
|
getUsers();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="w-full flex flex-col gap-8">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="bg-accent rounded-[4px] w-[10px] h-full"></div>
|
||||||
|
<h1 class="text-2xl font-semibold">Все пользователи</h1>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex flex-col p-4 rounded-box bg-base-300">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
{#if usersReady}
|
||||||
|
<table class="table">
|
||||||
|
<!-- head -->
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Токен</th>
|
||||||
|
<th>Имя</th>
|
||||||
|
<th>Баланс</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- row 1 -->
|
||||||
|
{#each users as user}
|
||||||
|
<tr class="hover:bg-neutral group">
|
||||||
|
<th class="font-normal">{user["token"]}</th>
|
||||||
|
<td class="font-semibold">{user["name"]} {user["surname"]}</td>
|
||||||
|
<td>{user["balance"]} USDT</td>
|
||||||
|
<td>
|
||||||
|
<a href={"/user/profile/"+user["token"]} class="btn btn-outline btn-info group-hover:btn-warning">Профиль</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{:else}
|
||||||
|
<div class="flex p-4 w-full justify-center items-center">
|
||||||
|
<span class="loading loading-spinner"></span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
3
static/robots.txt
Normal file
3
static/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
13
svelte.config.js
Normal file
13
svelte.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
13
tailwind.config.js
Normal file
13
tailwind.config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('daisyui'),
|
||||||
|
],
|
||||||
|
daisyui: {
|
||||||
|
themes: ["forest"],
|
||||||
|
},
|
||||||
|
};
|
7
vite.config.js
Normal file
7
vite.config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user