This commit is contained in:
you 2024-07-05 17:57:45 +03:00
parent 9ecbaab6c5
commit 94d9acf971
16 changed files with 623 additions and 16 deletions

11
package-lock.json generated
View File

@ -17,6 +17,7 @@
"@fontsource/fira-mono": "^4.5.10", "@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0", "@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@zerodevx/svelte-toast": "^0.9.5", "@zerodevx/svelte-toast": "^0.9.5",
@ -826,6 +827,16 @@
"@sveltejs/kit": "^2.0.0" "@sveltejs/kit": "^2.0.0"
} }
}, },
"node_modules/@sveltejs/adapter-static": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.2.tgz",
"integrity": "sha512-/EBFydZDwfwFfFEuF1vzUseBoRziwKP7AoHAwv+Ot3M084sE/HTVBHf9mCmXfdM9ijprY5YEugZjleflncX5fQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.5.18", "version": "2.5.18",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.18.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.18.tgz",

View File

@ -12,6 +12,7 @@
"@fontsource/fira-mono": "^4.5.10", "@fontsource/fira-mono": "^4.5.10",
"@neoconfetti/svelte": "^1.0.0", "@neoconfetti/svelte": "^1.0.0",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@zerodevx/svelte-toast": "^0.9.5", "@zerodevx/svelte-toast": "^0.9.5",

View File

@ -27,8 +27,12 @@ export async function checkAuth() {
export function checkAuthSync() { export function checkAuthSync() {
let result = getAuthInfo(); let result = getAuthInfo();
if (!result) return false; if (!result) return false;
try {
let decoded = jwtDecode(result.a); let decoded = jwtDecode(result.a);
return !(isPast(parseISO(new Date(decoded.exp*1000).toISOString()))); return !isPast(parseISO(new Date(decoded.exp * 1000).toISOString()));
} catch (e) {
return false;
}
} }
export const makeAuthHeaderForAxios = (jwt) => { export const makeAuthHeaderForAxios = (jwt) => {

View File

@ -1,6 +1,7 @@
<script> <script>
import { getAuthInfo } from "$lib/auth/Auth"; import { getAuthInfo, saveAuthInfo } from "$lib/auth/Auth";
import { redirect } from "$lib/tools/url/URLTools";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
</script> </script>
@ -51,6 +52,9 @@
</ul> </ul>
</div> </div>
<div class="navbar-end"> <div class="navbar-end">
<button class="btn btn-error btn-outline">Выход</button> <button on:click={()=>{
saveAuthInfo("");
redirect("/");
}} class="btn btn-error btn-outline">Выход</button>
</div> </div>
</div> </div>

View File

@ -0,0 +1,29 @@
<script>
export let totalPages;
export let pageChangedCallback;
export let disableButtons;
export let css;
let currentPage = 1;
</script>
<div class="join">
<button
on:click={() => {
if (currentPage === 1) return;
currentPage -= 1;
pageChangedCallback(currentPage);
}}
disabled={disableButtons}
class={"join-item btn "+css}</button
>
<button class={"join-item btn "+css}>Страница {currentPage}</button>
<button
on:click={() => {
if (currentPage === totalPages) return;
currentPage += 1;
pageChangedCallback(currentPage);
}}
disabled={disableButtons}
class={"join-item btn "+css}</button
>
</div>

2
src/routes/+layout.js Normal file
View File

@ -0,0 +1,2 @@
export const prerender = true;
export const ssr = false;

View File

@ -24,7 +24,7 @@
</script> </script>
{#if loggedIn} {#if loggedIn}
<div class="fixed left-0 right-0 top-0 flex"> <div class="fixed left-0 right-0 top-0 flex z-10">
<Navbar /> <Navbar />
</div> </div>
{/if} {/if}

View File

@ -0,0 +1,9 @@
<script>
import { onMount } from "svelte";
onMount(()=>{
document.getElementById("ref")?.click();
});
</script>
<a href={"/userslist"} class="hidden" id="ref"></a>

View File

@ -0,0 +1,304 @@
<script>
import { getAuthInfo, makeAuthHeaderForAxios } from "$lib/auth/Auth";
import { makePost } from "$lib/tools/requests/requests";
import { sayError, sayInfo } from "$lib/tools/toaster/Toaster";
import Pagination from "$lib/ui-components/pagination.svelte";
import { redirect } from "$lib/tools/url/URLTools";
import axios from "axios";
const disputesStatusMap = {
"4": "Требует проверки",
"5": "На проверке у трейдера",
"6": "Принят",
"7": "Отклонён",
};
const disputesStatusMapColors = {
"4": "text-white",
"5": "text-warning",
"6": "text-primary",
"7": "text-error",
};
//admin/getDisputes
let disputes = [];
let numOfPagesDisputes = 1;
let currentPageDisputes = 1;
let disablePagesDisputes = false;
async function getDisputes() {
disablePagesDisputes = true;
const result = await makePost(
"admin/getDisputes",
{
page: 1,
},
makeAuthHeaderForAxios(getAuthInfo()?.a)
);
console.log(result);
if (result.status === 401) {
sayError("Данные авторизации устарели");
redirect("/");
disablePagesDisputes = false;
// disablePagesUserDeposits = false;
return;
}
if (result.error) {
sayError("Не удалось получить депозиты пользователя");
disablePagesDisputes = false;
// disablePagesUserDeposits = false;
return;
}
disputes = result.data.disputes;
numOfPagesDisputes = result.data.pages;
disablePagesDisputes = false;
}
getDisputes();
let selectedDispute = {
amount: "",
is_sbp: "",
name: "",
order_creationtime: "",
order_id: "",
order_rate: "",
order_requisites_id: "",
order_status: "",
receipt: "",
requisite_cardnumber: "",
requisite_phone: "",
};
let showFullInfo = false;
async function acceptDispute() {
//admin/changeDispute
const result = await makePost(
"admin/changeDispute",
{
action: 1,
order_id: selectedDispute.order_id,
},
makeAuthHeaderForAxios(getAuthInfo()?.a)
);
console.log(result);
if (result.status === 401) {
sayError("Данные авторизации устарели");
redirect("/");
// disablePagesUserDeposits = false;
return;
}
if (result.error) {
sayError("Не удалось принять спор");
// disablePagesUserDeposits = false;
return;
}
sayInfo("Спор одобрен!");
showFullInfo = false;
getDisputes();
}
async function rejectDispute() {
const result = await makePost(
"admin/changeDispute",
{
action: 2,
order_id: selectedDispute.order_id,
},
makeAuthHeaderForAxios(getAuthInfo()?.a)
);
console.log(result);
if (result.status === 401) {
sayError("Данные авторизации устарели");
redirect("/");
// disablePagesUserDeposits = false;
return;
}
if (result.error) {
sayError("Не удалось отклонить спор");
// disablePagesUserDeposits = false;
return;
}
sayInfo("Спор отклонён!");
showFullInfo = false;
getDisputes();
}
function mimeToExtension(mimeType) {
const mimeTypes = {
"application/pdf": "pdf",
"application/zip": "zip",
"image/jpeg": "jpg",
"image/jpg": "jpg",
"image/png": "png",
"text/plain": "txt",
"application/msword": "doc",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
"docx",
// Добавьте дополнительные MIME-типы по необходимости
};
return mimeTypes[mimeType] || "bin"; // Если тип неизвестен, возвращаем .bin
}
</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 bg-base-300 p-4 rounded-box">
<div class="w-full flex flex-col justify-center items-center">
<Pagination
totalPages={numOfPagesDisputes}
pageChangedCallback={(n) => {
currentPageDisputes = n;
getDisputes();
}}
disableButtons={disablePagesDisputes}
css={"btn-neutral"}
/>
<p class="opacity-50 text-xs mt-1">
Всего страниц: {numOfPagesDisputes}
</p>
</div>
<div class="overflow-x-auto mt-4 flex flex-col">
{#if !disablePagesDisputes}
<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>
<!-- <th></th> -->
</tr>
</thead>
<tbody>
{#each disputes as dispute}
<tr class="hover:bg-neutral group">
<td class="font-semibold">{dispute["order_id"]}</td>
<td class={disputesStatusMapColors[dispute["order_status"]]}
>{disputesStatusMap[dispute["order_status"]]}</td
>
<td>{dispute["is_sbp"] === "t" ? "да" : "нет"}</td>
<td>{dispute["requisite_cardnumber"]}</td>
<td>{dispute["requisite_phone"]}</td>
<td>{dispute["order_rate"]}</td>
<td>{dispute["amount"]}</td>
<td>{dispute["order_creationtime"]}</td>
<td
on:click={() => {
selectedDispute = dispute;
showFullInfo = true;
}}
class="flex gap-2"
>
<button class="btn btn-info">Подробнее</button>
</td>
</tr>
{/each}
</tbody>
</table>
{:else}
<span class="loading loading-spinner self-center"></span>
{/if}
</div>
</div>
</div>
{#if showFullInfo}
<div
class="fixed inset-0 flex justify-center items-center md:p-32 p-8 pt-[68px] bg-black bg-opacity-50"
>
<div class="flex flex-col w-full max-w-[600px] p-4 rounded-box bg-base-300">
<p class="self-center opacity-60">ID: {selectedDispute.order_id}</p>
<p
class={"self-center text-3xl " +
disputesStatusMapColors[selectedDispute["order_status"]]}
>
{disputesStatusMap[selectedDispute["order_status"]]}
</p>
<p class="self-center opacity-60">{selectedDispute.order_creationtime}</p>
<div class="rounded-box flex flex-col bg-base-100 p-4 gap-1 mt-4">
<p class="text-lg font-bold">{selectedDispute.name}</p>
<p class="">Карта: {selectedDispute.requisite_cardnumber}</p>
<p class="">Телефон: {selectedDispute.requisite_phone}</p>
</div>
<div class="rounded-box flex flex-col bg-base-100 p-4 gap-1 mt-4">
<p class="text-lg font-bold">Курс</p>
<p>{selectedDispute.order_rate} RUB</p>
</div>
<div class="rounded-box flex flex-col bg-base-100 p-4 gap-1 mt-4">
<p class="text-lg font-bold">Сумма по факту</p>
<p>
{Number(selectedDispute.order_rate) * Number(selectedDispute.amount)} RUB
</p>
</div>
{#if selectedDispute["order_status"] === "4"}
<button
on:click={() => {
acceptDispute();
}}
class="btn btn-outline btn-primary mt-4"
>
Одобрить
</button>
<button on:click={() => {}} class="btn btn-outline btn-error mt-1">
Отклонить
</button>
{/if}
{#if selectedDispute["order_status"] !== "4"}
<button
on:click={() => {
axios
.get(
"https://test.0x000f.ru/api/v1/loadFile?dispute=" +
selectedDispute["order_id"],
{
responseType: "blob",
headers: makeAuthHeaderForAxios(getAuthInfo()?.a).headers,
}
)
.then(function (response) {
// Создаем ссылку для скачивания
const url = window.URL.createObjectURL(
new Blob([response.data])
);
const contentType = response?.data?.type; // Извлекаем MIME-тип из Blob
let extension = mimeToExtension(contentType); // Получаем расширение из функции
const a = document.createElement("a");
a.style.display = "none";
a.href = url;
a.download = `Чек_${Date.now()}.${extension}`;
document.body.appendChild(a);
a.click();
// Очищаем
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
})
.catch(function (error) {
console.error("Error:", error);
});
}}
class="btn btn-outline btn-info mt-4"
>
Скачать чек
</button>
{/if}
<button
on:click={() => {
showFullInfo = false;
}}
class="btn btn-outline btn-ghost mt-4"
>
Закрыть
</button>
</div>
</div>
{/if}

View File

@ -1 +0,0 @@
<h1 class="text-xl font-bold">BOO1</h1>

View File

@ -0,0 +1,14 @@
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 { reqID } = params;
return {
token: userToken,
reqID: reqID
};
}

View File

@ -0,0 +1,143 @@
<script>
// @ts-nocheck
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";
export let data;
let { reqID } = data;
let { token } = data;
let reqInfo = [];
let value_bankName;
let value_cardNumber;
let value_phone;
let value_status;
let canChange = false;
$: reqInfo, bindValues();
$: value_cardNumber, value_phone, value_status, checkFields();
function bindValues() {
value_bankName = reqInfo[0]?.bankname;
value_cardNumber = reqInfo[0]?.cardnumber;
value_phone = reqInfo[0]?.phone;
value_status = reqInfo[0]?.status === "t";
}
function checkFields() {
if(value_cardNumber?.length !== 16)
{
canChange = false;
return;
}
if(value_phone?.length !== 11)
{
canChange = false;
return;
}
canChange = true;
}
async function getReqInfo() {
const result = await makePost(
"getRequisite",
{
token: token,
},
makeAuthHeaderForAxios(getAuthInfo()?.a)
);
if (result.status === 401) {
sayError("Данные авторизации устарели");
window.location.href = "/";
return;
}
if (result.error) {
sayError("Не удалось получить реквизиты пользователя");
return;
}
console.log(result.data);
reqInfo = result.data.filter((s) => s.id === reqID);
}
async function changeRequisite() {
//changeRequisite
canChange = false;
const result = await makePost(
"changeRequisite",
{
...reqInfo[0],
bankname: value_bankName,
cardnumber: value_cardNumber,
phone: value_phone,
status: value_status ? "true":"false"
},
makeAuthHeaderForAxios(getAuthInfo()?.a)
);
if (result.status === 401) {
sayError("Данные авторизации устарели");
window.location.href = "/";
// canChange = true;
return;
}
if (result.error) {
sayError("Не удалось изменить реквизит пользователя");
canChange = true;
return;
}
document.getElementById("ref")?.click();
// canChange = true;
}
if (browser) {
getReqInfo();
}
</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">Изменение реквизита (ID: {reqID})</h1>
</div>
<div class="w-full flex flex-col p-4 gap-2 rounded-box bg-base-300">
{#each reqInfo as req}
<select
bind:value={value_bankName}
class="select select-bordered w-full text-base"
>
<option disabled selected value="-1">Банк</option>
<option value="sber">Сбер</option>
<!-- <option value="2">Модератор</option>
<option value="4">Администратор</option> -->
</select>
<label class="input input-bordered flex items-center gap-2">
Номер карты
<input
type="text"
class="grow text-info"
bind:value={value_cardNumber}
/>
</label>
<label class="input input-bordered flex items-center gap-2">
Телефон
<input type="text" class="grow text-info" bind:value={value_phone} />
</label>
<div class="flex p-4 items-center gap-2">
<p>Активный:</p>
<input
bind:checked={value_status}
type="checkbox"
class="toggle toggle-primary"
/>
</div>
<button on:click={()=>{changeRequisite()}} class="btn btn-primary" disabled={!canChange}>Сохранить</button>
<a href={"/user/profile/"+token} class="hidden" id="ref"></a>
{/each}
</div>
</div>

View File

@ -0,0 +1,12 @@
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;
return {
token: userToken,
};
}

View File

@ -0,0 +1,49 @@
<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 { token } = data;
let numOfOrders = 0;
</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">
<label class="input input-bordered flex items-center gap-2 max-w-lg">
Количество заявок:
<input
bind:value={numOfOrders}
type="number"
min={0}
class="grow text-info"
/>
</label>
{#each { length: numOfOrders } as _, index}
<div class="w-full flex flex-wrap items-center gap-2 p-4">
<p>Заявка #{index+1}</p>
<select class="select select-bordered w-full text-base">
<option disabled selected value="-1">Реквизит</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<label class="w-full input input-bordered flex items-center gap-2">
Сумма (USDT):
<input
type="number"
min={0}
class="grow text-info"
/>
</label>
</div>
{/each}
</div>
</div>

View File

@ -44,7 +44,7 @@
if (userResult.status === 401) { if (userResult.status === 401) {
userInfoReloading = false; userInfoReloading = false;
sayError("Данные авторизации устарели"); sayError("Данные авторизации устарели");
window.location.href = "/"; redirect("/");
return; return;
} }
if (userResult.error) { if (userResult.error) {
@ -84,6 +84,7 @@
return; return;
} }
userOrders = result.data.data; userOrders = result.data.data;
if(!Array.isArray(userOrders)) userOrders = [];
numPagesUserOrders = Number(result.data?.pages); numPagesUserOrders = Number(result.data?.pages);
if (numPagesUserOrders === 0) numPagesUserOrders = 1; if (numPagesUserOrders === 0) numPagesUserOrders = 1;
console.log(result.data); console.log(result.data);
@ -101,7 +102,7 @@
); );
if (result.status === 401) { if (result.status === 401) {
sayError("Данные авторизации устарели"); sayError("Данные авторизации устарели");
window.location.href = "/"; redirect("/");
return; return;
} }
if (result.error) { if (result.error) {
@ -109,6 +110,7 @@
return; return;
} }
userRequisites = result.data; userRequisites = result.data;
if(!Array.isArray(userDeposits)) userRequisites = [];
console.log(result.data); console.log(result.data);
} }
@ -134,7 +136,7 @@
); );
if (result.status === 401) { if (result.status === 401) {
sayError("Данные авторизации устарели"); sayError("Данные авторизации устарели");
window.location.href = "/"; redirect("/");
disablePagesUserDeposits = false; disablePagesUserDeposits = false;
return; return;
} }
@ -144,6 +146,7 @@
return; return;
} }
userDeposits = result.data.deposits; userDeposits = result.data.deposits;
if(!Array.isArray(userDeposits)) userDeposits = [];
numPagesUserOrders = result.data.pages; numPagesUserOrders = result.data.pages;
disablePagesUserDeposits = false; disablePagesUserDeposits = false;
// console.log(result.data, 'depos'); // console.log(result.data, 'depos');

View File

@ -1,4 +1,6 @@
import adapter from '@sveltejs/adapter-auto'; // import adapter from '@sveltejs/adapter-auto';
import adapter from "@sveltejs/adapter-static";
// import { prerendering } from 'svelte/internal';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
@ -6,8 +8,29 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // 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. // 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. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter() adapter: adapter({
} // default options are shown. On some platforms
// these options are set automatically — see below
pages: "build",
assets: "build",
fallback: undefined,
precompress: false,
strict: true,
}),
prerender: {
// default: true,
entries: [
"/user/change/sampleToken",
"/user/edit/req/sampleToken/sampleReqID",
"/user/neworder/sampleToken",
"/user/profile/sampleToken",
"/",
"/login",
"/newuser",
"/userslist",
],
},
},
}; };
export default config; export default config;