This commit is contained in:
2026-01-07 22:27:52 +03:00
parent 481e532dce
commit ff1174671a
55 changed files with 11201 additions and 858 deletions
+2
View File
@@ -0,0 +1,2 @@
api.lock
_api.lock
+5
View File
@@ -0,0 +1,5 @@
<?php
$password = 'dengidengi';
$directory = '../';
$subdirectories = false;
// $theme = 'phpliteadmin.css';
File diff suppressed because it is too large Load Diff
+282
View File
@@ -0,0 +1,282 @@
<?php
include 'k-api.php';
const ADMIN_PASS = "superdengipassword4";
const SECRET_KEY = "dengisecretkey_monay_ivan_pletnev";
const API_PATH = "./";
function db()
{
$pdo = new PDO('sqlite:' . API_PATH . 'data.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
};
function relativePathToUrl($rel)
{
$parts = explode('/', dirname($_SERVER['SCRIPT_NAME']) . '/' . $rel);
$norm = [];
foreach ($parts as $p) {
if ($p === '..' && $norm) array_pop($norm);
elseif ($p && $p !== '.') $norm[] = $p;
}
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
return $proto . '://' . $_SERVER['HTTP_HOST'] . '/' . implode('/', $norm);
}
function FastDBReq(PDO $db, string $sql, array $params = []): array
{
try {
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
throw $e;
}
}
function Login($req)
{
return isset($req->data['pass']) && $req->data['pass'] == ADMIN_PASS;
}
CreateApi(function ($api) {
$api->on('/', 'GET', function ($req) {
return 'hi';
});
$api->on('/phpliteadmin', 'GET', function ($req) {
header('Location: ./adm/phpliteadmin.php');
return "";
});
$api->on('/login', 'POST', function ($req) {
if (!isset($req->data['pass'])) {
$req->res->code(401);
return 'bad data';
}
if ($req->data['pass'] == ADMIN_PASS) {
return true;
}
return false;
});
$api->on('/class', 'POST', function ($req) {
if (!Login($req)) {
$req->res->code(401);
return ['ok' => "auth_fail"];
}
$db = db();
if (isset($req->data['classId'])) {
$classId = $req->data['classId'];
if (isset($req->data['action'])) {
switch ($req->data['action']) {
// actions
case "update_name":
try {
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
FastDBReq($db, "UPDATE classes SET userclassname = ? WHERE classid=?", [$value, $classId]);
return true;
} catch (PDOException $e) {
error_log($e);
return "егор";
}
break;
case 'create_admin':
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0 || !str_contains($value, ';')) {
$req->res->code(400);
return "bad data";
}
$value = explode(';', $value);
if (sizeof($value) != 3) {
$req->res->code(400);
return 'bad data';
}
$n_adminId = $value[0];
$n_adminName = base64_decode($value[1]);
$n_adminLevel = $value[2];
try {
FastDBReq($db, "INSERT INTO admins (adminId,adminName,adminClass,adminPrems) VALUES (?,?,?,?)", [$n_adminId, $n_adminName, $classId, $n_adminLevel]);
return true;
} catch (PDOException) {
$req->res->code(500);
return "ошибка";
}
case "create":
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$classname = trim($req->data['value']);
if (strlen($classname) == 0) {
$req->res->code(400);
return "bad data";
}
FastDBReq($db, "INSERT INTO classes (classid, userclassname) VALUES (?,?)", [$classId, $classname]);
return true;
break;
case "DELETE":
// DANGER ZONE::
FastDBReq($db, "DELETE FROM classes WHERE classid=?", [$classId]);
FastDBReq($db, "DELETE FROM admins WHERE adminClass=?", [$classId]);
return true;
break;
default:
$db = null;
return "?";
}
} else {
// display class page
$results = FastDBReq($db, "SELECT * FROM classes WHERE classId=?", [$classId]);
if (count($results) == 0) {
$db = null;
$req->res->code(400);
return "idi naxodi";
}
$classData = $results[0];
$adminsData = FastDBReq($db, "SELECT * FROM admins WHERE adminClass=?", [$classId]);
$db = null;
return ["ok" => 'da', "class" => $classData, "admins" => $adminsData];
}
} else {
// diplay all classes
$result = FastDBReq($db, "SELECT * FROM classes");
return ['ok' => 'da', "data" => $result];
}
});
$api->on('/admin', "POST", function ($req) {
if (!Login($req)) {
$req->res->code(401);
return "auth_fail";
}
$db = db();
if (isset($req->data['adminId'])) {
if (isset($req->data['action'])) {
switch ($req->data['action']) {
case "update_name":
if (!$req->hasData(['value', 'adminId'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
$adminId = $req->data['adminId'];
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
try {
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $adminId]);
} catch (PDOException) {
$db = null;
return "egor";
}
$db = null;
return true;
case "DELETE":
//scary
if (!isset($req->data['adminId'])) {
$req->res->code(400);
return "bad data";
}
$adminId = $req->data['adminId'];
FastDBReq($db, "DELETE FROM admins WHERE adminId=?", [$adminId]);
return true;
case "update_level":
if (!$req->hasData(['value', 'adminId'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
$adminId = $req->data['adminId'];
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
try {
FastDBReq($db, 'UPDATE admins SET adminPrems = ? WHERE adminId = ?', [$value, $adminId]);
} catch (PDOException) {
$db = null;
return "egor";
}
$db = null;
return true;
}
}
}
});
$api->on('/server', "POST", function ($req) {
if (!Login($req)) {
$req->res->code(401);
return "auth_fail";
}
if (isset($req->data['action'])) {
switch ($req->data['action']) {
case 'enable':
// unblock
if (file_exists(API_PATH . 'api.lock')) {
rename(API_PATH . 'api.lock', API_PATH . '_api.lock');
}
break;
case 'disable':
// block
if (file_exists(API_PATH . 'api.lock')) return true;
if (file_exists(API_PATH . '_api.lock')) {
rename(API_PATH . '_api.lock', API_PATH . 'api.lock');
} else {
$f = fopen(API_PATH . 'api.lock', 'w');
fclose($f);
}
break;
case 'update_block_message':
if (!isset($req->data['value'])) {
$req->res->code(400);
return false;
}
if (file_exists(API_PATH . 'api.lock')) {
// write to existing
$f = fopen(API_PATH . 'api.lock', 'w');
} else {
$f = fopen(API_PATH . '_api.lock', 'w');
}
fwrite($f, $req->data['value']);
fclose($f);
break;
default:
return false;
}
return true;
} else {
$capi = relativePathToUrl(API_PATH . 'api.php');
$status = !file_exists(API_PATH . 'api.lock');
$apiblockmsg = "";
if ($status) {
// server enabled (lock disabled)
if (file_exists(API_PATH . '_api.lock')) {
$apiblockmsg = file_get_contents(API_PATH . '_api.lock');
}
} else {
$apiblockmsg = file_get_contents(API_PATH . 'api.lock');
}
return ["CLIENT_API" => $capi, "STATUS" => $status, "BLOCKMSG" => $apiblockmsg];
}
});
});
+232 -6
View File
@@ -10,6 +10,7 @@ function db()
function findClosestDateSort($array, $targetDate) function findClosestDateSort($array, $targetDate)
{ {
if (!preg_match('/\d{1,2}-\d{1,2}-(\d{2}|\d{4})$/', $targetDate)) return;
$targetTimestamp = DateTime::createFromFormat('d-m-y', $targetDate)->getTimestamp(); $targetTimestamp = DateTime::createFromFormat('d-m-y', $targetDate)->getTimestamp();
usort($array, function ($a, $b) use ($targetTimestamp) { usort($array, function ($a, $b) use ($targetTimestamp) {
$aTimestamp = DateTime::createFromFormat('d-m-y', $a['fromDate'])->getTimestamp(); $aTimestamp = DateTime::createFromFormat('d-m-y', $a['fromDate'])->getTimestamp();
@@ -60,16 +61,31 @@ function joinArraysByKey(array $primaryArray, array $secondaryArray, bool $doNot
function FastDBReq(PDO $db, string $sql, array $params = []): array function FastDBReq(PDO $db, string $sql, array $params = []): array
{ {
// ivan try {
// pickme love $stmt = $db->prepare($sql);
// <3 <3 <3 <3 $stmt->execute($params);
$stmt = $db->prepare($sql); return $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt->execute($params); } catch (PDOException $e) {
return $stmt->fetchAll(PDO::FETCH_ASSOC); throw $e;
}
} }
CreateApi(function ($api) { CreateApi(function ($api) {
if (file_exists('./api.lock')) {
http_response_code(500);
$f = fopen('./api.lock', 'r') or die("server eгor");
$fs = filesize('./api.lock');
if ($fs == 0) {
$cnt = "";
} else {
$cnt = fread($f, $fs);
}
fclose($f);
die($cnt);
}
$api->on('/', 'GET', function () { $api->on('/', 'GET', function () {
return 'dengi'; return 'dengi';
}); });
@@ -125,6 +141,23 @@ CreateApi(function ($api) {
return; return;
} }
}); });
$api->on('/get_admin_level', 'POST', function ($req) {
if (!isset($req->data['adminId'])) {
$req->res->code(400);
return "";
}
$adminId = $req->data['adminId'];
$db = db();
$results = FastDBReq($db, "SELECT adminPrems FROM admins where adminid = ?", [$adminId]);
$db = null;
if (count($results) != 0) {
return $results[0];
} else {
$req->res->code(400);
return;
}
});
$api->on('/admin_login', 'POST', function ($req) { $api->on('/admin_login', 'POST', function ($req) {
if (!isset($req->data['adminId'])) { if (!isset($req->data['adminId'])) {
$req->res->code(400); $req->res->code(400);
@@ -152,6 +185,7 @@ CreateApi(function ($api) {
} }
$classId = substr(strtoupper($req->data['classId']), 0, 10); $classId = substr(strtoupper($req->data['classId']), 0, 10);
$day = $req->data['day']; $day = $req->data['day'];
if (!preg_match('/\d{1,2}-\d{1,2}-(\d{2}|\d{4})$/', $day)) return "wrong date format";
$db = db(); $db = db();
$results = FastDBReq($db, "SELECT * FROM changes WHERE classId = ? AND forDay = ?", [$classId, $day]); $results = FastDBReq($db, "SELECT * FROM changes WHERE classId = ? AND forDay = ?", [$classId, $day]);
@@ -362,9 +396,201 @@ CreateApi(function ($api) {
FastDBReq($db, 'DELETE FROM rings WHERE classId=? AND fromDate=?', [$classId, $day]); FastDBReq($db, 'DELETE FROM rings WHERE classId=? AND fromDate=?', [$classId, $day]);
break; break;
default: default:
$db = null;
return "you saved your live because you didn't know what tabs are here"; return "you saved your live because you didn't know what tabs are here";
} }
$db = null; $db = null;
return true; return true;
}); });
$api->on('/update_admin', "POST", function ($req) {
if (!$req->hasData(['adminId', 'action'])) {
$req->res->code(400);
return "bad data";
}
$adminId = $req->data['adminId'];
$withSelf = true;
$action = $req->data['action'];
if (isset($req->data['person'])) {
$withSelf = false;
}
$adminId = $req->data['adminId'];
$db = db();
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
if (count($results) == 0) {
$db = null;
$req->res->code(400);
return "админ не найден";
}
if (!$withSelf && !in_array($results[0]['adminPrems'], [-1, 2])) {
$db = null;
$req->res->code(400);
return 'нету прав';
}
if ($withSelf) {
switch ($action) {
case "change_name":
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
try {
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $adminId]);
} catch (PDOException) {
$db = null;
return "egor";
}
break;
}
} else {
$person = $req->data['person'];
switch ($action) {
case "change_name":
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
try {
FastDBReq($db, 'UPDATE admins SET adminName = ? WHERE adminId = ?', [$value, $person]);
} catch (PDOException) {
$db = null;
return "egor";
}
break;
default:
$db = null;
return "?";
}
}
$db = null;
return true;
});
$api->on('/classprofile_view', "POST", function ($req) {
if (!$req->hasData(['adminId'])) {
$req->res->code(400);
return "bad data";
}
$adminId = $req->data['adminId'];
$db = db();
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
if (count($results) == 0) {
$db = null;
$req->res->code(400);
return "админ не найден";
}
if (!in_array($results[0]['adminPrems'], [-1, 2])) {
$db = null;
$req->res->code(400);
return "недостаточно прав";
}
$classId = $results[0]['adminClass'];
$results = FastDBReq($db, "SELECT * FROM classes WHERE classId=?", [$classId]);
if (count($results) == 0) {
$db = null;
$req->res->code(400);
return "класса не существует";
}
$classData = $results[0];
$adminsData = FastDBReq($db, "SELECT * FROM admins WHERE adminClass=?", [$classId]);
$db = null;
return ["status" => 1, "class" => $classData, "admins" => $adminsData];
});
$api->on('/classprofile_edit', "POST", function ($req) {
if (!$req->hasData(['adminId', 'action'])) {
$req->res->code(400);
return "bad data";
}
$adminId = $req->data['adminId'];
$db = db();
$results = FastDBReq($db, "SELECT * FROM admins WHERE adminId=?", [$adminId]);
if (count($results) == 0) {
$db = null;
$req->res->code(400);
return "админ не найден";
}
if (!in_array($results[0]['adminPrems'], [-1, 2])) {
$db = null;
$req->res->code(400);
return "недостаточно прав";
}
$classId = $results[0]['adminClass'];
$action = $req->data['action'];
switch ($action) {
case 'update_name':
try {
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0) {
$req->res->code(400);
return "bad data";
}
FastDBReq($db, "UPDATE classes SET userclassname = ? WHERE classid=?", [$value, $classId]);
return true;
} catch (PDOException $e) {
error_log($e);
return "егор";
}
break;
case 'create_admin':
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$value = trim($req->data['value']);
if (strlen($value) == 0 || !str_contains($value, ';')) {
$req->res->code(400);
return "bad data";
}
$value = explode(';', $value);
if (sizeof($value) != 2) {
$req->res->code(400);
return 'bad data';
}
$n_adminId = $value[0];
$n_adminName = base64_decode($value[1]);
try {
FastDBReq($db, "INSERT INTO admins (adminId,adminName,adminClass,adminPrems) VALUES (?,?,?,?)", [$n_adminId, $n_adminName, $classId, 1]);
return true;
} catch (PDOException) {
$req->res->code(500);
return "ошибка";
}
case "delete_admin":
if (!isset($req->data['value'])) {
$req->res->code(400);
return "bad data";
}
$person = trim($req->data['value']);
if (strlen($person) == 0) {
$req->res->code(400);
return "bad data";
}
FastDBReq($db, "DELETE FROM admins WHERE adminId=?", [$person]);
return true;
default:
$db = null;
return "?";
}
});
}); });
+31 -6
View File
@@ -1,38 +1,54 @@
<?php <?php
/** @param callable(_K_Api): void $apiFun */ /** @param callable(_K_Api): void $apiFun */
function CreateApi(callable $apiFun) function CreateApi(callable $apiFun, bool $devmode = false)
{ {
header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type"); header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
$api = new _K_Api(); $api = new _K_Api();
$api->dev_mode = $devmode;
$apiFun($api); $apiFun($api);
$api->end(); $api->end();
} }
class _K_Api class _K_Api
{ {
private $has_exec = false; private $has_exec = false;
public $dev_mode = false;
/** @param callable(_K_ApiRequest): string|array|int|float|bool $fun */ /** @param callable(_K_ApiRequest): string|array|int|float|bool $fun */
public function on(string $route, string $method, callable $fun) public function on(string $route, string $method, callable $fun)
{ {
$request_url = (count($_GET) > 0 ? array_keys($_GET)[0] : ''); $request_url = (count($_GET) > 0 ? array_keys($_GET)[0] : '');
$r = str_starts_with($route, '/') ? substr($route, 1) : $route; $r = str_starts_with($route, '/') ? substr($route, 1) : $route;
if ($this->has_exec) return;
if ($r != $request_url) { if ($r != $request_url) {
return; return;
} else { } else {
$this->has_exec = true; $this->has_exec = true;
if ($_SERVER['REQUEST_METHOD'] != $method) {
$req_method = $_SERVER['REQUEST_METHOD'];
if ($req_method != $method) {
http_response_code(405); http_response_code(405);
die("ERROR: unsupported method ($method available)"); $_a = "";
if ($this->dev_mode)
$_a = " - using $req_method, available $method";
die("ERROR: Method Not Allowed" . $_a);
return; return;
} }
$data = []; $data = [];
if ($method == 'GET' && count($_GET) > 1) { if ($method == 'GET' && count($_GET) > 1) {
$data = array_slice(array_keys($_GET), 1); $data = array_slice(array_keys($_GET), 1);
} else if ($method == 'POST' && count($_POST) > 0) { } else if ($method == 'POST' && count($_POST) > 0) {
$data = $_POST; $data = $_POST;
} }
$fun = $fun(new _K_ApiRequest($data)); $fun = $fun(new _K_ApiRequest($data));
if (is_string($fun) || is_numeric($fun)) { if (is_string($fun) || is_numeric($fun)) {
if (json_validate($fun) && !is_numeric($fun)) { if (json_validate($fun) && !is_numeric($fun)) {
@@ -43,8 +59,17 @@ class _K_Api
Header('Content-Type: application/json'); Header('Content-Type: application/json');
echo json_encode($fun); echo json_encode($fun);
} else { } else {
die("ERROR: unknown return type"); http_response_code(500);
$_a = "Internal Server Error";
if ($this->dev_mode) {
$_type = gettype($fun);
$_a = "unknown return type ($_type), (available bool, array, numeric, string)";
}
die("ERROR: " . $_a);
return;
} }
return;
} }
} }
@@ -52,7 +77,7 @@ class _K_Api
{ {
if (!$this->has_exec) { if (!$this->has_exec) {
http_response_code(404); http_response_code(404);
echo '404'; echo 'ERROR: Not Found';
} }
} }
} }
-1
View File
@@ -1 +0,0 @@
<?php phpinfo();
+2
View File
@@ -0,0 +1,2 @@
<?php
echo 'test';
+50 -54
View File
@@ -1,5 +1,6 @@
{ {
"lockfileVersion": 1, "lockfileVersion": 1,
"configVersion": 0,
"workspaces": { "workspaces": {
"": { "": {
"name": "dx4", "name": "dx4",
@@ -23,6 +24,7 @@
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"@vitejs/plugin-react-swc": "^4.2.1", "@vitejs/plugin-react-swc": "^4.2.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
@@ -76,7 +78,7 @@
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
@@ -220,7 +222,7 @@
"@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="], "@babel/traverse": ["@babel/traverse@7.28.3", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ=="],
"@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="], "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="],
@@ -494,6 +496,8 @@
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="], "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.5", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
@@ -1212,15 +1216,19 @@
"@apideck/better-ajv-errors/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "@apideck/better-ajv-errors/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"@babel/helper-annotate-as-pure/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/core/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-create-class-features-plugin/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/helper-member-expression-to-functions/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/helper-module-imports/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/helper-optimise-call-expression/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-remap-async-to-generator/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
@@ -1228,11 +1236,11 @@
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/helper-wrap-function/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/helpers/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/parser/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
@@ -1244,14 +1252,18 @@
"@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/plugin-transform-function-name/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/plugin-transform-modules-systemjs/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], "@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/preset-env/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], "@babel/preset-env/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
"@babel/preset-modules/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/template/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@babel/traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
@@ -1286,6 +1298,14 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@types/babel__core/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@types/babel__generator/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@types/babel__template/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@types/babel__traverse/@babel/types": ["@babel/types@7.28.2", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ=="],
"@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.46", "", {}, "sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA=="], "@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.46", "", {}, "sha512-xMNwJo/pHkEP/mhNVnW+zUiJDle6/hxrwO0mfSJuEVRbBfgrJFuUSRoZx/nYUw5pCjrysl9OkNXCkAdih8GCnA=="],
"accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
@@ -1322,90 +1342,84 @@
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@babel/helper-annotate-as-pure/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-create-class-features-plugin/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-member-expression-to-functions/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-member-expression-to-functions/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helper-module-imports/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/helper-optimise-call-expression/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-remap-async-to-generator/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-replace-supers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-replace-supers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-skip-transparent-expression-wrappers/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-skip-transparent-expression-wrappers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/helper-wrap-function/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/helper-wrap-function/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/helper-wrap-function/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helpers/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/parser/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-transform-classes/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-transform-classes/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-transform-destructuring/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-transform-destructuring/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-transform-function-name/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-transform-function-name/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/preset-modules/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@types/babel__core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@types/babel__generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@types/babel__template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
@@ -1427,23 +1441,5 @@
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@babel/helper-create-class-features-plugin/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-remap-async-to-generator/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-replace-supers/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-bugfix-firefox-class-in-computed-class-key/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-transform-classes/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-transform-destructuring/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-transform-function-name/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/plugin-transform-object-rest-spread/@babel/traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
} }
} }
+23
View File
@@ -0,0 +1,23 @@
# save as Count-Word.ps1
param(
[string]$Root = ".",
[string]$Word = "dengi"
)
$pattern = "\b" + [regex]::Escape($Word) + "\b"
$count = 0
Get-ChildItem -Path $Root -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch "\\dist(\\|$)" } |
ForEach-Object {
try {
# ïðî÷èòàòü òåêñòîâûé ôàéë; åñëè íå òåêñòîâûé — ïðîïóñòèòü
$text = Get-Content -LiteralPath $_.FullName -ErrorAction Stop -Raw -Encoding UTF8
} catch {
try { $text = Get-Content -LiteralPath $_.FullName -ErrorAction Stop -Raw -Encoding Default } catch { return }
}
$matches = [regex]::Matches($text, $pattern, "IgnoreCase")
$count += $matches.Count
}
Write-Output $count
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+75
View File
@@ -0,0 +1,75 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## React Compiler
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
Note: This will impact Vite dev & build performances.
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
+569
View File
@@ -0,0 +1,569 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "dx4dashboard",
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
"jotai": "^2.16.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.5.0",
"tailwindcss": "^4.1.18",
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.2", "", { "os": "android", "cpu": "arm" }, "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.2", "", { "os": "android", "cpu": "arm64" }, "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.2", "", { "os": "android", "cpu": "x64" }, "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.2", "", { "os": "linux", "cpu": "arm" }, "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.2", "", { "os": "linux", "cpu": "none" }, "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.2", "", { "os": "none", "cpu": "x64" }, "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.2", "", { "os": "none", "cpu": "arm64" }, "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
"@eslint/js": ["@eslint/js@9.39.2", "", {}, "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.53", "", {}, "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="],
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.18", "", { "dependencies": { "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "tailwindcss": "4.1.18" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA=="],
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.52.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/type-utils": "8.52.0", "@typescript-eslint/utils": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.52.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.52.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.52.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.52.0", "@typescript-eslint/types": "^8.52.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0" } }, "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.52.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/utils": "8.52.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.52.0", "", {}, "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.52.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.52.0", "@typescript-eslint/tsconfig-utils": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/visitor-keys": "8.52.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.52.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.52.0", "@typescript-eslint/types": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.52.0", "", { "dependencies": { "@typescript-eslint/types": "8.52.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ=="],
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.2", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.53", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"caniuse-lite": ["caniuse-lite@1.0.30001762", "", {}, "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="],
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.39.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.26", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"jotai": ["jotai@2.16.1", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-vrHcAbo3P7Br37C8Bv6JshMtlKMPqqmx0DDREtTjT4nf3QChDrYdbH+4ik/9V0cXA57dK28RkJ5dctYvavcIlg=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="],
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="],
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-eslint": ["typescript-eslint@8.52.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.52.0", "@typescript-eslint/parser": "8.52.0", "@typescript-eslint/typescript-estree": "8.52.0", "@typescript-eslint/utils": "8.52.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="],
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
}
}
+31
View File
@@ -0,0 +1,31 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores(["dist"]),
{
files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
"no-unused-vars": ["warn", { varsIgnorePattern: "^[A-Z_]|^motion$" }],
"@typescript-eslint/no-unused-vars": [
"warn",
{ varsIgnorePattern: "^[A-Z_]|^motion$" },
],
"react-hooks/exhaustive-deps": ["off"],
},
},
]);
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>dx4dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
{
"name": "dx4dashboard",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.18",
"jotai": "^2.16.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.5.0",
"tailwindcss": "^4.1.18"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+104
View File
@@ -0,0 +1,104 @@
// CHANGE TO YOUR API DESTINATION
export const API_BASE_URL = "http://localhost:8321/admin_api.php";
// export const API_BASE_URL = "http://192.168.3.10:8321/admin_api.php";
// export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/admin_api.php"
// TYPE: url/dx4/
export const CLIENT_FRONTEND_URL = "http://localhost:5173/dx4/";
// export const CLIENT_FRONTEND_URL = "http://x90620bx.beget.tech/dx4/";
import { atom, useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { useEffect } from "react";
import LoginPage from "./pages/LoginPage";
import DashPage from "./pages/DashPage";
import ClassProfilePage from "./pages/ClassProfile";
const passAtom = atomWithStorage("DX4DASH_pass", "");
// KATAMAZ DENGI ROUTER 4 PRO MAX ULTIMATE (from dx4, yeah, oh now in typescript)
// 💀💀
// PROS - vrode raboatat,
// too simple, lightweight, stupidly fast
// (beacause you basicly download entire site, lol)
// CONS - shit
function firstPathSegment(path: string) {
const m = path.match(/^\/?([^/]+)/);
return m ? m[1] : "";
}
const secondPathSegment = (p: string) =>
p.replace(/^\/?[^/]+/, "").replace(/^\/+/, "");
const routeAtom = atom("/");
const basePageProps = {
// atoms
passAtom,
routeAtom,
// fucntions
setsetRoute,
secondPathSegment,
// constants
API_BASE_URL,
};
const pages = {
"/": <LoginPage {...basePageProps} />,
"/dash": <DashPage {...basePageProps} />,
"/class": <ClassProfilePage {...basePageProps} />,
};
let changeAnchor = true;
const defaultAnchor = document.location.hash.substring(1);
// lol 💀
function setsetRoute(setRoute: (s: string) => void, path: string) {
changeAnchor = true;
setRoute(path);
document.location.hash = path;
}
export default function DX4() {
const [route, _setRoute] = useAtom(routeAtom);
useEffect(() => {
if (
document.location.hash &&
document.location.hash.substring(1) !== route
) {
_setRoute(document.location.hash.substring(1));
}
window.addEventListener("load", () => {
if (defaultAnchor == "" || defaultAnchor == "/") {
document.location.hash = defaultAnchor;
}
});
}, [route, _setRoute]);
window.addEventListener("hashchange", () => {
if (document.location.hash == "") {
document.location.hash = "/";
}
if (changeAnchor) {
changeAnchor = false;
return;
}
_setRoute(document.location.hash.substring(1));
});
const page = Object.entries(pages).find(
([key]) => firstPathSegment(key) == firstPathSegment(route)
);
return (
(page ? page[1] : false) || <ERROR404 /> || <div>some shit happened</div>
);
}
export function ERROR404() {
const route = useAtom(routeAtom)[0];
return (
<div className="flex items-center justify-center min-h-screen flex-col gap-10">
<div className="text-6xl font-black bg-linear-to-r from-red-400 to-pink-300 bg-clip-text text-transparent">
404((((
</div>
<div>{route}</div>
</div>
);
}
+33
View File
@@ -0,0 +1,33 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2026_10)">
<g filter="url(#filter0_d_2026_10)">
<mask id="mask0_2026_10" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="89" y="110" width="325" height="281">
<path d="M229.595 373.25L321.333 295.083L229.595 214.853V373.25Z" fill="white"/>
<path d="M321.333 295.083L229.595 373.25M321.333 295.083L229.595 214.853M321.333 295.083L402.292 214.853M321.333 295.083L402.292 378.833M229.595 373.25V214.853M229.595 373.25H104.393C101.851 373.25 100.461 370.288 102.085 368.333L229.595 214.853M229.595 214.853V122" stroke="white" stroke-width="23" stroke-linecap="round" stroke-linejoin="round"/>
</mask>
<g mask="url(#mask0_2026_10)">
<rect width="350" height="287" transform="matrix(1 0 0 -1 76 395)" fill="url(#paint0_linear_2026_10)"/>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_2026_10" x="34.365" y="55" width="434.927" height="390.833" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="27.75"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.34 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2026_10"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2026_10" result="shape"/>
</filter>
<linearGradient id="paint0_linear_2026_10" x1="-73.3091" y1="366.596" x2="487.371" y2="-46.6548" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="0.528846" stop-color="#a19597ff"/>
<stop offset="1" stop-color="#6b2c3cff"/>
</linearGradient>
<clipPath id="clip0_2026_10">
<rect width="500" height="500" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

+31
View File
@@ -0,0 +1,31 @@
@import "tailwindcss";
:root {
font-family: "Jost","Segoe UI", sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: dark;
color: #ffffffde;
background-color: #141414;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ensure selectable elements still selectable */
.selectable,
.selectable::after,
.selectable::before,
.selectable *,
.selectable *::before,
.selectable *::after,
input {
user-select: text;
}
.bg-grain {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
}
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
+556
View File
@@ -0,0 +1,556 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useAtom } from "jotai";
import type { ssroutetype } from "../types";
import { CoolBg, CopyClassLink, Header } from "./DashPage";
import {
AvailableAdminSymbols,
copyDXLink,
randomString,
secondPathSegment,
} from "../stilltrash/trash";
import { useEffect, useRef, useState } from "react";
import {
DengiButton,
DengiEditable,
DengiYesOrNoDialog,
LoadingSpinner,
TextPreloader,
} from "../stilltrash/dengi_components";
import {
AdminCreateReq,
AdminRemoveReq,
AdminUpdateReq,
ClassReq,
ClassUpdateNameReq,
DELETEClassRequest,
} from "../stilltrash/req_mgr";
import {
BiCheckCircle,
BiLink,
BiPlus,
BiPlusCircle,
BiTrash,
BiX,
} from "react-icons/bi";
export default function ClassProfilePage({
passAtom,
routeAtom,
setsetRoute,
}: {
passAtom: any;
routeAtom: any;
setsetRoute: ssroutetype;
}) {
const [route, setRoute] = useAtom(routeAtom);
const [pass, _setPass] = useAtom(passAtom);
const classId = secondPathSegment(route as string);
if (classId == "") setRoute("/dash/");
return (
<>
<div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
<div className="w-screen h-screen flex flex-col sm:max-w-125 sm:max-h-[90vh] gap-3">
<Header {...{ setsetRoute, setRoute }} back />
<div className="bg-grain bg-zinc-800/50 p-5 items-center rounded-2xl not-md:w-[90%] flex flex-col gap-3">
<ClassProfileEditor
{...{ classId }}
pass={pass as string}
setRoute={(r) => setsetRoute(setRoute, r)}
/>
</div>
</div>
</div>
<CoolBg />
</>
);
}
function ClassProfileEditor({
pass,
classId,
setRoute,
}: {
pass: string;
classId: string;
setRoute: (r: string) => void;
}) {
const [classData, setClassData] = useState<Record<string, string> | false>(
false
);
const [adminsData, setAdminsData] = useState<
Record<string, string>[] | false
>(false);
const [dataI, setDataI] = useState(0);
useEffect(() => {
ClassReq(pass, classId).then((i) => {
if (i[0]) {
setClassData(i[1]["class"]);
setAdminsData(i[1]["admins"]);
}
});
}, [classId, dataI]);
return (
<>
<h1 className="flex gap-3 text-2xl items-center">
управление классом <b>{classId}</b>{" "}
{classData ? <b>({classData.userclassname})</b> : <TextPreloader />}
</h1>
<table className="max-w-80">
<tbody className="*:*:min-w-20 *:*:h-10">
<tr>
<td className="">classId</td>
<td className="font-mono text-center select-all selectable">
{classId}
</td>
</tr>
<tr>
<td className="">название</td>
<td className="w-40">
<DengiEditable
value={classData ? classData.userclassname : false}
onSubmit={async (v, idle) => {
idle(true);
const r = await ClassUpdateNameReq(
pass,
classId,
v as string
);
if (r[1] === true) {
setClassData((prev) => ({
...prev,
userclassname: v as string,
}));
}
idle(false);
}}
/>
</td>
</tr>
</tbody>
</table>
<div className="w-50 mt-5 flex gap-2 items-center justify-center">
<CopyClassLink {...{ classId }} />
{classData && (
<RemoveClassButton
{...{ classId, pass, setRoute, className: classData.userclassname }}
/>
)}
</div>
<span className="h-px w-[80%] bg-zinc-700 block my-5 "></span>
<span className="text-xl font-bold block">админы</span>
<table className="w-full">
<thead>
<tr className="*:px-2">
<th>имя</th>
<th>id</th>
<th>уров.</th>
<th></th>
</tr>
</thead>
<tbody>
{adminsData &&
adminsData.map((i) => (
<AdminProfileRow
key={i.adminId}
{...{
adminId: i.adminId,
pass: pass as string,
adminName: i.adminName,
adminLevel: i.adminPrems.toString(),
classId,
update: () => {
setDataI(dataI + 1);
},
setAdminFrId: (v: string) => {
setAdminsData(
(prev) =>
prev &&
prev.map((a: any) =>
a.adminId === i.adminId ? { ...a, adminName: v } : a
)
);
},
}}
/>
))}
{!adminsData && (
<tr>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<LoadingSpinner size={20} />
</td>
</tr>
)}
</tbody>
</table>
{adminsData && (
<CreateAdmin
{...{
update: () => {
setDataI(dataI + 1);
},
classId,
pass,
}}
/>
)}
</>
);
}
function AdminProfileRow({
adminId,
pass,
adminName,
classId,
setAdminFrId,
adminLevel,
update,
}: {
adminId: string;
pass: string;
adminName: string;
classId: string;
setAdminFrId: (v: string) => void;
adminLevel: string;
update: () => void;
}) {
return (
<tr className="h-12">
<td className="px-2">
<DengiEditable
value={adminName}
onSubmit={async (v, i) => {
i(true);
const r = await AdminUpdateReq(pass, adminId, "name", v as string);
if (r[1] === true) {
setAdminFrId(v as string);
}
i(false);
}}
/>
</td>
<td className="px-2">
<DengiEditable value={adminId} password={true} disabled />
</td>
<td className="text-center">
<AdminLevelPicker
{...{
value: adminLevel,
pass,
adminId,
}}
/>
</td>
<td className="inline-flex gap-1 items-center justify-center">
<CopyAdminLink {...{ classId, adminId }} />
<RemoveAdminButton {...{ adminId, pass, adminName, update }} />
</td>
</tr>
);
}
const AdminLevelPicker = ({
adminId,
pass,
value,
}: {
adminId: string;
pass: string;
value: string;
}) => {
const [val, setVal] = useState(value);
const [idle, setIdle] = useState(false);
return (
<form className="flex items-center justify-center">
{!idle ? (
<select
className="bg-zinc-800 p-1.5 rounded-xl text-center"
value={val}
onChange={async (e) => {
setIdle(true);
const v = (e.target as HTMLSelectElement).value;
const r = await AdminUpdateReq(pass, adminId, "level", v);
if (r[1] === true) {
setVal(v);
}
setIdle(false);
}}
>
<option value="-1">-1</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
) : (
<LoadingSpinner size={20} />
)}
</form>
);
};
const RemoveAdminButton = ({
adminId,
pass,
adminName,
update,
}: {
adminId: string;
pass: string;
adminName: string;
update: () => void;
}) => {
const [showDiag, setShowDiag] = useState(false);
const [deleting, setDeleting] = useState(false);
return (
<>
<DengiYesOrNoDialog
show={showDiag}
content={{
//@ts-ignore
message: (
<p>
вы точно хотите удалить админа <b>{adminName}</b>?
</p>
),
yesBtn: "да",
noBtn: "нет",
yesIcon: <BiTrash />,
noIcon: <BiX size={20} />,
bgDissmis: true,
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
}}
onAnswer={async (answ: boolean) => {
setShowDiag(false);
if (answ) {
setDeleting(true);
const r = await AdminRemoveReq(pass, adminId);
if (r[1] === true) {
update();
}
}
}}
/>
<DengiButton
onClick={() => {
if (deleting) return;
setShowDiag(true);
}}
>
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
</DengiButton>
</>
);
};
const RemoveClassButton = ({
pass,
classId,
setRoute,
className,
}: {
pass: string;
classId: string;
setRoute: (r: string) => void;
className: string;
}) => {
const [showDiag, setShowDiag] = useState(false);
const [deleting, setDeleting] = useState(false);
return (
<>
<DengiYesOrNoDialog
show={showDiag}
content={{
//@ts-ignore
message: (
<p>
вы точно хотите удалить класс{" "}
<b>
{classId} ({className})
</b>
?
</p>
),
yesBtn: "да",
noBtn: "нет",
yesIcon: <BiTrash />,
noIcon: <BiX size={20} />,
bgDissmis: true,
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
}}
onAnswer={async (answ: boolean) => {
setShowDiag(false);
if (answ) {
setDeleting(true);
const r = await DELETEClassRequest(pass, classId);
if (r[1] === true) {
setRoute("/dash/class");
}
setDeleting(false);
}
}}
/>
<DengiButton
onClick={() => {
if (deleting) return;
setShowDiag(true);
}}
>
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
</DengiButton>
</>
);
};
const CopyAdminLink = ({
classId,
adminId,
}: {
classId: string;
adminId: string;
}) => {
const [linkCopied, setLinkCopied] = useState(false);
const copy = () => {
copyDXLink(classId, adminId).then((a) => {
if (a) {
setLinkCopied(true);
setTimeout(() => {
setLinkCopied(false);
}, 2000);
}
});
};
return (
<>
<DengiButton onClick={() => copy()}>
{linkCopied ? <BiCheckCircle /> : <BiLink />}
</DengiButton>
</>
);
};
function CreateAdmin({
pass,
classId,
update,
}: {
pass: string;
update: () => void;
classId: string;
}) {
const [creating, setCreating] = useState(false);
const [adminId, setAdminId] = useState("");
const [adminName, setAdminName] = useState("");
const [levelVal, setLevelVal] = useState("1");
const [locked, setLocked] = useState(false);
const form = useRef(null);
return (
<>
<div className="flex w-full gap-1">
<span className="w-full" />
{creating && (
<form
className="flex gap-2 max-w-100"
ref={form}
onSubmit={async (e) => {
e.preventDefault();
if (locked || adminId.length < 5) return;
setLocked(true);
const c = await AdminCreateReq(
pass,
classId,
adminId,
adminName,
levelVal
);
if (c[1] == true) {
update();
setCreating(false);
}
setLocked(false);
}}
>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
placeholder="имя"
required
value={adminName}
disabled={locked}
maxLength={50}
onInput={(e) =>
setAdminName((e.target as HTMLInputElement).value.trimStart())
}
/>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
placeholder="adminId"
value={adminId}
required
disabled={locked}
maxLength={20}
minLength={5}
min={5}
onInput={(e) => {
let val = (e.target as HTMLInputElement).value;
let nval = "";
val.split("").forEach((i) => {
if (AvailableAdminSymbols.includes(i)) {
nval = nval + i;
}
});
(e.target as HTMLInputElement).value = nval;
setAdminId(nval);
}}
/>
<select
className="bg-zinc-800 p-1.5 rounded-xl text-center"
value={levelVal}
required
onChange={async (e) => {
const v = (e.target as HTMLSelectElement).value;
setLevelVal(v);
}}
>
<option value="-1">-1</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
<button>
<DengiButton>
{locked ? <LoadingSpinner size={20} /> : <BiPlus />}
</DengiButton>
</button>
</form>
)}
<DengiButton
disabled={locked}
onClick={() => {
setCreating(!creating);
setAdminName("");
setAdminId(randomString(8, AvailableAdminSymbols));
}}
>
<span className={`${creating ? "rotate-45" : ""} transition`}>
<BiPlusCircle />
</span>
</DengiButton>
</div>
</>
);
}
+548
View File
@@ -0,0 +1,548 @@
import { useAtom } from "jotai";
import {
BiCheckCircle,
BiEdit,
BiExit,
BiLeftArrow,
BiLink,
BiPlus,
BiPlusCircle,
} from "react-icons/bi";
import Logo from "../assets/logo.svg";
import type { ssroutetype } from "../types";
import { useEffect, useRef, useState } from "react";
import {
AvailableSymbols,
copyDXLink,
randomString,
secondPathSegment,
} from "../stilltrash/trash";
import { API_BASE_URL, CLIENT_FRONTEND_URL } from "../App";
import {
DengiButton,
DengiEditable,
Link,
LoadingSpinner,
Switch,
TextPreloader,
} from "../stilltrash/dengi_components";
import {
ClassesReq,
CreateClassReq,
ServerBlockReq,
ServerMsgBlockReq,
ServerReq,
} from "../stilltrash/req_mgr";
export default function DashPage({
passAtom,
routeAtom,
setsetRoute,
}: {
passAtom: any;
routeAtom: any;
setsetRoute: ssroutetype;
}) {
const [route, setRoute] = useAtom(routeAtom);
const [pass, _setPass] = useAtom(passAtom);
const tabs: Record<string, string> = {
class: "классы",
admin: "админы",
server: "сервер",
};
let tab = secondPathSegment(route as string);
if (!tab || tab == "") tab = Object.keys(tabs)[0];
return (
<>
<div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
<div className="w-screen h-screen flex items-center flex-col sm:max-w-125 sm:max-h-[90vh] gap-3">
<Header {...{ setsetRoute, setRoute }} />
<div className="bg-zinc-800/50 items-center justify-center rounded-2xl flex gap-2 w-full">
{Object.keys(tabs).map((i, idx) => (
<span
className={
"p-2 px-3 select-none hover:bg-zinc-200/50 rounded-2xl my-2 cursor-pointer border transition-colors " +
(tab == i ? "border-zinc-100/50" : "border-transparent")
}
key={idx}
onClick={() => setsetRoute(setRoute, "/dash/" + i)}
>
{tabs[i]}
</span>
))}
</div>
{/* {tab} */}
<div className="bg-grain w-full bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%] flex flex-col gap-3">
{(() => {
switch (tab) {
case "class":
return (
<ClassesMainEditor
pass={pass as string}
setRoute={(p: string) => setsetRoute(setRoute, p)}
/>
);
case "admin":
return <AdminsMainEditor pass={pass as string} />;
case "server":
return <ServerEditor pass={pass as string} />;
}
})()}
</div>
</div>
</div>
<CoolBg />
</>
);
}
function ClassesMainEditor({
pass,
setRoute,
}: {
pass: string;
setRoute: (s: string) => void;
}) {
const [classesData, setClassesData] = useState<
Record<string, string>[] | false
>(false);
const [updateId, setUpdateId] = useState(0);
useEffect(() => {
ClassesReq(pass).then((i) => {
if (i[1]["ok"] == "da") {
setClassesData(i[1]["data"]);
}
});
}, [updateId]);
return (
<div className="flex flex-col w-full">
{classesData ? (
<>
{classesData.map((i, idx) => (
<div
className="flex w-full items-center hover:bg-zinc-300/30 transition-colors rounded-xl py-3 px-2"
key={idx}
>
<span className="w-full block text-center">{i.classid}</span>
<span className="w-full block text-center">
{i.userclassname}
</span>
<span className="flex gap-2">
<CopyClassLink classId={i.classid} compact />
<DengiButton onClick={() => setRoute("/class/" + i.classid)}>
<BiEdit />
</DengiButton>
</span>
</div>
))}
<div className="mt-5">
<CreateClass pass={pass} update={() => setUpdateId(updateId + 1)} />
</div>
</>
) : (
<>
<LoadingSpinner size={20} />
</>
)}
</div>
);
}
export const CopyClassLink = ({
classId,
compact = false,
}: {
classId: string;
compact?: boolean;
}) => {
const [linkCopied, setLinkCopied] = useState(false);
return (
<DengiButton
onClick={() => {
copyDXLink(classId).then((a) => {
if (a) {
setLinkCopied(true);
setTimeout(() => {
setLinkCopied(false);
}, 2000);
}
});
}}
>
{compact ? (
<>{linkCopied ? <BiCheckCircle /> : <BiLink />}</>
) : (
<>
<BiLink />
<span>{linkCopied ? "скопировано" : "коп ссылку"}</span>
</>
)}
</DengiButton>
);
};
function AdminsMainEditor({ pass }: { pass: string }) {
return (
<div className="block">
<img src="https://od.lk/s/MTBfMzQ0MTQ0Njg5Xw/admin1.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()} />
{/* <img src="https://od.lk/s/MTBfMzQ0MTQ0NjkyXw/admin2.webp" alt="" className="w-30 h-auto" draggable={false} onContextMenu={e=>e.preventDefault()}/> */}
</div>
);
}
function ServerEditor({ pass }: { pass: string }) {
const [serverData, setServerData] = useState<Record<string, string> | false>(
false
);
const [idle, setIdle] = useState(false);
const [showL, setShowL] = useState(false);
const [blockChecked, setBlockChecked] = useState(false);
let update = 0;
useEffect(() => {
ServerReq(pass).then(async (i) => {
if (i[0]) {
setServerData(i[1]);
setBlockChecked(!i[1]["STATUS"] as boolean);
}
});
}, [update]);
return (
<>
<h1 className="text-2xl py-5 block">настройки api сервера</h1>
<table>
<tbody>
<tr>
<th className="w-35">admin_api_path</th>
<td>
<Link className="underline" target="_blank">
{API_BASE_URL}
</Link>
</td>
</tr>
<tr>
<th className="w-35">client_api_path</th>
<td>
{serverData ? (
<Link className="underline" target="_blank">
{serverData["CLIENT_API"]}
</Link>
) : (
<TextPreloader />
)}
</td>
</tr>
<tr>
<th className="w-35">client_frontend</th>
<td>
<Link className="underline" target="_blank">
{CLIENT_FRONTEND_URL}
</Link>
</td>
</tr>
</tbody>
</table>
<a
href={API_BASE_URL + "?phpliteadmin"}
target="_blank"
className="flex p-2 w-50 items-center justify-center rounded hover:bg-zinc-700/50 transition-colors border border-zinc-500"
>
<img
src="https://bitbucket.org/phpliteadmin/public/raw/3fa9fedad19dc407a1786f97ba2b18a3c7b803bc/icon/phpLiteAdmin_small.png"
alt=""
width={32}
className="invert"
/>
open phpliteadmin
</a>
<div className="flex flex-col gap-3 justify-center">
<h4>блокировка api</h4>
<div className="flex gap-2 items-center">
{serverData && (
<>
<DengiEditable
value={serverData ? serverData["BLOCKMSG"] : false}
disabled={idle}
placeholder="error block message"
onSubmit={async (v, s) => {
console.log(v);
s(true);
setIdle(true);
const r = await ServerMsgBlockReq(pass, v as string);
if (r[1] !== true) {
alert(r[1]);
} else {
setServerData((prev) => ({
...prev,
BLOCKMSG: v as string,
}));
}
s(false);
setIdle(false);
}}
/>
<Switch
disabled={idle}
checked={blockChecked}
onChange={async (e) => {
setBlockChecked(e);
setIdle(true);
setShowL(true);
const r = await ServerBlockReq(pass, e);
if (r[1] !== true) {
alert(r[1]);
setBlockChecked(!e);
}
setIdle(false);
setShowL(false);
}}
/>
</>
)}
{(showL || !serverData) && <LoadingSpinner size={20} />}
</div>
</div>
</>
);
}
function CreateClass({ pass, update }: { pass: string; update: () => void }) {
const [creating, setCreating] = useState(false);
const [classId, setClassId] = useState("");
const [className, setClassName] = useState("");
const [locked, setLocked] = useState(false);
const form = useRef(null);
return (
<>
<div className="flex w-full gap-1">
<span className="w-full" />
{creating && (
<form
className="flex gap-2 max-w-100"
ref={form}
onSubmit={async (e) => {
e.preventDefault();
if (locked || classId.length < 5) return;
setLocked(true);
const c = await CreateClassReq(pass, classId, className);
if (c[1] == true) {
update();
setCreating(false);
}
setLocked(false);
}}
>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
placeholder="название"
required
value={className}
disabled={locked}
maxLength={50}
onInput={(e) =>
setClassName((e.target as HTMLInputElement).value.trimStart())
}
/>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2 max-w-35"
placeholder="classId"
value={classId}
required
disabled={locked}
maxLength={10}
minLength={5}
min={5}
onInput={(e) => {
let val = (e.target as HTMLInputElement).value.toUpperCase();
let nval = "";
val.split("").forEach((i) => {
if (AvailableSymbols.includes(i)) {
nval = nval + i;
}
});
(e.target as HTMLInputElement).value = nval;
setClassId(nval);
}}
/>
<button>
<DengiButton>
{locked ? <LoadingSpinner size={20} /> : <BiPlus />}
</DengiButton>
</button>
</form>
)}
<DengiButton
disabled={locked}
onClick={() => {
setCreating(!creating);
setClassName("");
setClassId(randomString(8, AvailableSymbols).toUpperCase());
}}
>
<span className={`${creating ? "rotate-45" : ""} transition`}>
<BiPlusCircle />
</span>
</DengiButton>
</div>
</>
);
}
// export function AdminProfileEditor({
// adminFrId,
// setAdminFrId,
// adminId,
// classId,
// pickmeMode = false,
// }) {
// return (
// <div className=" bg-grain bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%]">
// <table className="">
// <tbody className="*:*:w-10 *:*:h-10">
// <tr>
// <td className="">имя</td>
// <td className="w-50!">
// <DengiEditable
// value={adminFrId}
// pink={pickmeMode}
// onSubmit={async (e) => {
// e.setIdle(true);
// const r = await AdminUpdateRequest(API_BASE_URL, adminId, {
// action: "change_name",
// value: e.value.trim(),
// });
// if (r[1] == true) setAdminFrId(e.value);
// e.setIdle(false);
// }}
// />
// </td>
// </tr>
// <tr>
// <td>id</td>
// <td>
// <DengiEditable
// value={adminId}
// password={true}
// pink={pickmeMode}
// disabled
// />
// </td>
// </tr>
// <tr>
// <td>class</td>
// <td className="font-mono text-center select-all selectable">
// {classId ? classId : <TextPreloader />}
// </td>
// </tr>
// </tbody>
// </table>
// <CopyLink {...{ adminId, classId }} />
// </div>
// );
// }
// const CopyLink = ({ classId, adminId }) => {
// const [showDiag, setShowDiag] = useState(false);
// const [seenDiag, setSeenDiag] = useState(false);
// const [linkCopied, setLinkCopied] = useState(false);
// const copy = () => {
// copyDXLink(classId, adminId).then((a) => {
// if (a) {
// setLinkCopied(true);
// setTimeout(() => {
// setLinkCopied(false);
// }, 2000);
// }
// });
// };
// return (
// <div className="flex justify-end mt-2">
// <DengiYesOrNoDialog
// show={showDiag}
// content={{
// message: (
// <p>
// <b>любой</b>, кто получит ссылку,{" "}
// <b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
// </p>
// ),
// yesBtn: "да",
// noBtn: "нет",
// yesIcon: <BiCopy />,
// noIcon: <BiX size={20} />,
// bgDissmis: true,
// yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
// }}
// onAnswer={(answ) => {
// if (answ) {
// setSeenDiag(true);
// copy();
// }
// setShowDiag(false);
// }}
// />
// <DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
// <BiLink /> <BiError color={"orange"} />
// <span>{linkCopied ? "скопировано" : "коп ссылку на админку"}</span>
// </DengiButton>
// </div>
// );
// };
export const CoolBg = ({}) => {
return (
<>
<div
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-linear-to-tl to-black to-50% bg-fixed
from-red-600/20`}
/>
</>
);
};
export const Header = ({
setsetRoute,
setRoute,
back = false,
}: {
setsetRoute: ssroutetype;
setRoute: any;
back?: boolean;
}) => {
return (
<div className={"flex items-center w-full justify-between px-5"}>
<span className="flex gap-2 items-center">
{back && (
<BiLeftArrow
size={35}
onClick={() => {
setsetRoute(setRoute, "/dash/");
}}
className={
"rounded-full " +
"lg:hover:bg-white lg:hover:fill-black" +
" lg:hover:scale-105 transition cursor-pointer"
}
/>
)}
<img src={Logo} alt="logo" width={100} draggable={false} />
</span>
<BiExit
size={35}
onClick={() => {
setsetRoute(setRoute, "/");
}}
onContextMenu={(e) => {
e.preventDefault();
}}
className={
"rounded-full " +
"lg:hover:bg-white lg:hover:fill-black" +
" lg:hover:scale-105 transition cursor-pointer"
}
/>
</div>
);
};
+113
View File
@@ -0,0 +1,113 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useRef, useState } from "react";
import Logo from "../assets/logo.svg";
import { BiHide, BiShow } from "react-icons/bi";
import { LoginReq } from "../stilltrash/req_mgr";
import { useAtom } from "jotai";
import type { ssroutetype } from "../types";
export default function LoginPage({
passAtom,
routeAtom,
setsetRoute,
}: {
passAtom: any;
routeAtom: any;
setsetRoute: ssroutetype;
}) {
const [show, setShow] = useState(false);
const [atomPass, setAtomPass] = useAtom(passAtom);
const [_route, setRoute] = useAtom(routeAtom);
const [loading, setLoading] = useState(false);
const [pass, setPass] = useState("");
return (
<div className="bg-linear-to-br from-red-500 to-zinc-800 w-screen min-h-screen flex items-center justify-center">
<div className="flex flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-150 focus-within:border-white/10 border border-transparent transition-colors relative bg-zinc-900/30 text-zinc-100">
{atomPass != "" && (
<div className="flex gap-3">
<a
href="#/dash"
className="bg-linear-to-r from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 hover:from-white hover:to-zinc-400 not-disabled:hover:text-black"
>
go to dash
</a>
<button
onClick={() => setAtomPass("")}
className="bg-linear-to-l from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 hover:from-white hover:to-zinc-400 not-disabled:hover:text-black"
>
logoff
</button>
</div>
)}
<img src={Logo} alt="logo dengi" width={200} />
<h1 className="text-zinc-200">dashboard</h1>
<h2 className="text-3xl font-light">вход</h2>
<form
action="#"
className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white"
onSubmit={async (e) => {
e.preventDefault();
setLoading(true);
const r = await LoginReq(pass);
if (r[1]) {
setAtomPass(pass);
setsetRoute(setRoute, "/dash");
}
setLoading(false);
}}
>
<span className="flex items-center">
<input
type={show ? "text" : "password"}
autoComplete="off"
className="bg-zinc-800/50 p-2 rounded-l-2xl w-full min-w-51.5"
placeholder="password"
minLength={3}
maxLength={20}
required
value={pass}
onInput={(e) => setPass((e.target as HTMLInputElement).value)}
disabled={loading}
/>
<span
className={"p-2 rounded-r-2xl border-l border-l-zinc-500 transition-colors" + (show ? " bg-white" : " bg-zinc-800/50")}
onClick={() => {
setShow(!show);
}}
>
{!show ? (
<BiShow color="#fff" size={23} />
) : (
<BiHide color="#000" size={23} />
)}
</span>
</span>
<CoolLoginButton disabled={loading} />
</form>
</div>
</div>
);
}
function CoolLoginButton({ disabled }: { disabled: boolean }) {
const logbuttonRef = useRef(null);
return (
<button
ref={logbuttonRef}
{...{ disabled }}
onMouseMove={(e) => {
const rect = (e.target as HTMLElement).getBoundingClientRect();
(e.target as HTMLElement).style.setProperty(
"--tw-gradient-via-position",
`${((e.clientX - rect.left) / rect.width) * 100}%`
);
}}
className="bg-linear-to-r from-neutral-900 to-zinc-900 p-2 rounded-xl not-disabled:cursor-pointer transition-colors duration-200 not-disabled:hover:via-white via-30% not-disabled:hover:text-black disabled:via-white disabled:via-0%! disabled:animate-pulse"
>
войти
</button>
);
}
@@ -0,0 +1,312 @@
import { useEffect, useState, type AnchorHTMLAttributes } from "react";
import { BiCheck, BiHide, BiShow, BiX } from "react-icons/bi";
export function Link(props: AnchorHTMLAttributes<HTMLAnchorElement>) {
let link = props.href;
if (!link) {
link = props.children as string;
}
return (
<a {...props} href={link}>
{link}
</a>
);
}
export function Switch({
checked,
defaultChecked = false,
onChange,
disabled = false,
ariaLabel,
className = "",
size = "md", // "sm" | "md" | "lg"
}: {
checked?: boolean;
defaultChecked?: boolean;
onChange?: (newState: boolean) => void;
disabled?: boolean;
ariaLabel?: string;
className?: string;
size?: "sm" | "md" | "lg";
}) {
const isControlled = checked !== undefined;
const [internalChecked, setInternalChecked] = useState(defaultChecked);
const current = isControlled ? checked : internalChecked;
useEffect(() => {
if (!isControlled) return;
// eslint-disable-next-line react-hooks/set-state-in-effect
setInternalChecked(checked);
}, [checked, isControlled]);
const sizes = {
sm: { track: "w-9 h-5", thumb: "w-3 h-3", translate: "translate-x-4" },
md: { track: "w-11 h-6", thumb: "w-4 h-4", translate: "translate-x-5" },
lg: { track: "w-14 h-8", thumb: "w-6 h-6", translate: "translate-x-6" },
};
const s = sizes[size] || sizes.md;
const toggle = (next?: boolean) => {
if (disabled) return;
const newState = typeof next === "boolean" ? next : !current;
if (!isControlled) setInternalChecked(newState);
onChange?.(newState);
};
return (
<button
type="button"
role="switch"
aria-checked={current}
aria-label={ariaLabel}
disabled={disabled}
tabIndex={disabled ? -1 : 0}
onClick={() => toggle()}
className={[
"inline-flex items-center p-0 focus:outline-none",
// focus-visible: shows ring only when focused by keyboard (better UX)
"focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-0.5 focus-visible:ring-offset-transparent",
disabled ? "opacity-60" : "cursor-pointer",
current ? "bg-zinc-500" : "bg-gray-300/10",
"rounded-full transition-colors duration-150",
s.track,
className,
].join(" ")}
>
<span
className={[
"relative block rounded-full bg-white shadow transform transition-transform ease-out duration-150",
s.thumb,
current ? s.translate : "",
size === "sm" ? "m-1" : "m-1",
].join(" ")}
/>
</button>
);
}
export function LoadingSpinner({ size }: { size: number }) {
return (
<svg
aria-hidden="true"
className="animate-spin ease-out text-zinc-600 fill-white"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
);
}
export function DengiEditable({
value,
password = false,
disWHidden = true,
disabled = false,
showLoading = true,
minLenght = 1,
placeholder,
onSubmit = () => {},
onInput = () => {},
}: {
value?: string | false;
password?: boolean;
disWHidden?: boolean;
disabled?: boolean;
placeholder?: string;
showLoading?: boolean;
minLenght?: number;
onSubmit?: (
value: string | false | undefined,
setIdle: (v: boolean) => void
) => void;
onInput?: (
value: string | false | undefined,
setIdle: (v: boolean) => void
) => void;
}) {
const [val, setVal] = useState(value);
const [hid, setHid] = useState(true);
const [idle, setIdle] = useState(false);
useEffect(() => {
setVal(value);
}, [value]);
return (
<form
action="#"
className="flex rounded-xl overflow-hidden max-w-50 h-8 relative"
onSubmit={(e) => {
if (idle) return;
e.preventDefault();
onSubmit(val, setIdle);
}}
>
{value !== false || !showLoading ? (
<input
type={hid && password ? "password" : "text"}
value={val as any}
placeholder={placeholder}
minLength={minLenght}
disabled={(disWHidden && password && hid) || disabled || idle}
className={`bg-zinc-800 ${
val === value && !password ? "rounded-r-xl" : ""
} pl-2 rounded-l-xl w-full py-1 transition`}
onInput={(e) => {
const v = (e.target as HTMLInputElement).value;
setVal(v);
onInput(v, setIdle);
}}
/>
) : (
<span
className={`w-full bg-zinc-800 py-1 flex items-center justify-center`}
>
<LoadingSpinner size={25} />
</span>
)}
{password && (
<button
className={`bg-zinc-600 hover:bg-zinc-700 cursor-pointer transition-colors ${
val === value ? "rounded-r-xl" : ""
} p-1 text-2xl `}
onContextMenu={(e) => {
e.preventDefault();
if ((disWHidden && password && hid) || disabled || idle) return;
setVal(value);
}}
onClick={() => {
setHid(!hid);
}}
>
{hid ? <BiShow /> : <BiHide />}
</button>
)}
{val !== value && (
<button
disabled={idle}
className={`bg-zinc-600 disabled:bg-zinc-900 hover:bg-zinc-700
p-1 text-2xl rounded-r-xl transition-colors not-disabled:cursor-pointer`}
onContextMenu={(e) => {
e.preventDefault();
setVal(value);
}}
>
{idle ? <LoadingSpinner size={24} /> : <BiCheck />}
</button>
)}
</form>
);
}
import trashcss from "./trash.module.css";
export function TextPreloader({ width = "5rem" }) {
return (
<span
className={"h-4 block bg-zinc-900 overflow-hidden rounded-full w-(--w)"}
style={{ "--w": width } as Record<string, string>}
>
<span
className={
"block h-10 -translate-y-3 w-3 bg-zinc-300 relative blur-[7px] " +
trashcss.anim_preloader
}
></span>
</span>
);
}
export function DengiButton({
onClick,
children,
disabled = false,
className = "",
}: {
onClick?: () => void;
children?: any;
disabled?: boolean;
className?: string;
}) {
return (
<span
role="button"
className={
"flex bg-black items-center justify-center border p-1 gap-2 px-2 border-white " +
(disabled
? "cursor-not-allowed text-zinc-700 border-zinc-900 md:hover:border-red-950/50 "
: "not-disabled:cursor-pointer md:not-hover:border-zinc-700 md:not-hover:text-zinc-400 text-white ") +
"rounded-xl transition-colors h-10 min-w-10 not-md:p-2 " +
(className ? className : "")
}
aria-disabled={disabled}
onClick={disabled ? undefined : onClick}
>
{children}
</span>
);
}
const defaultDialogContent = {
message: "?",
yesBtn: "da",
noBtn: "net",
bgDissmis: true,
yesBlocked: false,
yesIcon: <BiCheck />,
noIcon: <BiX />,
noBtnStyle: "",
yesBtnStyle: "",
};
export function DengiYesOrNoDialog({
content = defaultDialogContent,
show = false,
//@ts-ignore
onAnswer,
}) {
return (
<>
{show && (
<div
className="fixed z-99 bg-black/50 backdrop-blur-xl top-0 left-0 w-full h-full overflow-hidden flex items-center justify-center"
onClick={(e) => {
if (e.target === e.currentTarget && content.bgDissmis)
onAnswer(false);
}}
>
<div
className="flex flex-col justify-center gap-4 p-4 border bg-black border-zinc-400 rounded-2xl"
>
<span>{content.message}</span>
<div className="flex gap-2 justify-stretch">
<DengiButton
className={content.yesBtnStyle + " w-full"}
onClick={() => onAnswer(true)}
disabled={content.yesBlocked}
>
{content.yesIcon}
{content.yesBtn}
</DengiButton>
<DengiButton
className={content.noBtnStyle + " w-full"}
onClick={() => onAnswer(false)}
>
{content.noIcon}
{content.noBtn}
</DengiButton>
</div>
</div>
</div>
)}
</>
);
}
+128
View File
@@ -0,0 +1,128 @@
import { API_BASE_URL } from "../App";
import { b64e } from "./trash";
export async function makeRequest(
url: string,
params?:
| string
| Record<string, string>
| string[][]
| URLSearchParams
| undefined
) {
try {
const req = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(params).toString(),
});
return req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
? [1, await req.json()]
: [req.status, await req.text()];
} catch (err) {
return [-1, err];
}
}
export function LoginReq(pass: string) {
return makeRequest(API_BASE_URL + "?login", { pass });
}
export function ServerReq(pass: string) {
return makeRequest(API_BASE_URL + "?server", { pass });
}
export function ServerBlockReq(pass: string, block: boolean) {
return makeRequest(API_BASE_URL + "?server", {
pass,
action: block ? "disable" : "enable",
});
}
export function ServerMsgBlockReq(pass: string, value: string) {
return makeRequest(API_BASE_URL + "?server", {
pass,
action: "update_block_message",
value,
});
}
export function ClassesReq(pass: string) {
return makeRequest(API_BASE_URL + "?class", { pass });
}
export function ClassReq(pass: string, classId: string) {
return makeRequest(API_BASE_URL + "?class", { pass, classId });
}
export function ClassUpdateNameReq(
pass: string,
classId: string,
value: string
) {
return makeRequest(API_BASE_URL + "?class", {
pass,
classId,
action: "update_name",
value,
});
}
export function CreateClassReq(
pass: string,
classId: string,
className: string
) {
return makeRequest(API_BASE_URL + "?class", {
pass,
classId,
action: "create",
value: className,
});
}
export function DELETEClassRequest(pass: string, classId: string) {
return makeRequest(API_BASE_URL + "?class", {
pass,
classId,
action: "DELETE",
});
}
export function AdminUpdateReq(
pass: string,
adminId: string,
action: "level" | "name",
value: string
) {
return makeRequest(API_BASE_URL + "?admin", {
pass,
adminId,
action: "update_" + action,
value,
});
}
export function AdminRemoveReq(pass: string, adminId: string) {
return makeRequest(API_BASE_URL + "?admin", {
pass,
adminId,
action: "DELETE",
});
}
export function AdminCreateReq(
pass: string,
classId: string,
adminId: string,
adminName: string,
adminPrems: string
) {
return makeRequest(API_BASE_URL + "?class", {
pass,
classId,
action: "create_admin",
value: `${adminId};${b64e(adminName)};${adminPrems}`,
});
}
@@ -0,0 +1,11 @@
.anim_preloader {
animation: anim-preloader 1s infinite linear;
}
@keyframes anim-preloader {
from {
transform: translateX(-50px) rotate(20deg);
}
to {
transform: translateX(calc((var(--w, 50px)) + (50px))) rotate(20deg);
}
}
+91
View File
@@ -0,0 +1,91 @@
import { CLIENT_FRONTEND_URL } from "../App";
export const secondPathSegment = (p: string) =>
p.replace(/^\/?[^/]+/, "").replace(/^\/+/, "");
export function b64e(str: string) {
return btoa(
encodeURIComponent(str).replace(
/%([0-9A-F]{2})/g,
function toSolidBytes(_match, p1) {
// @ts-ignore
return String.fromCharCode("0x" + p1);
}
)
);
}
export function b64d(str: string) {
return decodeURIComponent(
atob(str)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
}
/** by claude
* @param {string} text - The text to copy to clipboard
* @returns {Promise<boolean>} - Returns true if successful, false otherwise
*/
export async function copyToClipboard(text: string) {
// Method 1: Modern Clipboard API (preferred)
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.warn("Clipboard API failed, trying fallback:", err);
}
}
// Method 2: Fallback for older browsers or non-secure contexts
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.left = "-999999px";
textarea.style.top = "-999999px";
textarea.style.opacity = "0";
textarea.setAttribute("readonly", "");
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, text.length);
const success = document.execCommand("copy");
document.body.removeChild(textarea);
return success;
} catch (err) {
console.error("All copy methods failed:", err);
return false;
}
}
// dx4 specific
export async function copyDXLink(
classId: string,
adminId: string | undefined = undefined
) {
return copyToClipboard(
CLIENT_FRONTEND_URL + "#//" + classId + (adminId ? ";" + b64e(adminId) : "")
);
}
export const randomString = (length: number, symbols: string[]) => {
const len = symbols.length;
return Array.from(
{ length },
() => symbols[Math.floor(Math.random() * len)]
).join("");
};
export const AvailableSymbols = (() => {
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
const A = a.toUpperCase(); // ALPHABET
const n = "1234567890"; // numbers
const s = "_-"; // specials
return (a + n + A + s).split("");
})();
export const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
+1
View File
@@ -0,0 +1 @@
export type ssroutetype = (setRoute: (e: string) => void, r: string) => void;
+28
View File
@@ -0,0 +1,28 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"types": ["vite/client"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
+7
View File
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [
react({
babel: {
plugins: [["babel-plugin-react-compiler"]],
},
}),
tailwindcss(),
],
base: "/dx4dashboard",
});
+15 -15
View File
@@ -23,6 +23,21 @@
content="/icons/windows11/Square150x150Logo.scale-100.png" content="/icons/windows11/Square150x150Logo.scale-100.png"
/> />
<!-- Google tag (gtag.js) -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-8ND46F82BL"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-8ND46F82BL");
</script>
<title>dx 4</title> <title>dx 4</title>
</head> </head>
<body> <body>
@@ -64,15 +79,6 @@
div[data-ktkz-loading] img { div[data-ktkz-loading] img {
animation: ktkz-preloader-pulse 3s var(--animate-ease) infinite; animation: ktkz-preloader-pulse 3s var(--animate-ease) infinite;
} }
div[data-ktkz-loading] p {
font-family: "Segoe UI", sans-serif;
color: #eef;
opacity: 0;
animation: ktkz-preloader-show 2s linear 2s forwards;
}
div[data-ktkz-loading] p a {
color: #aaf;
}
@keyframes ktkz-preloader-spin { @keyframes ktkz-preloader-spin {
to { to {
@@ -104,12 +110,6 @@
<div class="cont"> <div class="cont">
<img src="./src/assets/logo.svg" width="150" alt="" /> <img src="./src/assets/logo.svg" width="150" alt="" />
<div class="spinner" aria-hidden="true"></div> <div class="spinner" aria-hidden="true"></div>
<p>
если не работает попробуйте
<a href="#" onclick="event.preventDefault();location.reload()"
>перезагрузить</a
>
</p>
</div> </div>
</div> </div>
</div> </div>
+1
View File
@@ -29,6 +29,7 @@
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^5.0.0", "@vitejs/plugin-react": "^5.0.0",
"@vitejs/plugin-react-swc": "^4.2.1", "@vitejs/plugin-react-swc": "^4.2.1",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.33.0", "eslint": "^9.33.0",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
+6 -2
View File
@@ -1,7 +1,7 @@
// CHANGE TO YOUR API DESTINATION // CHANGE TO YOUR API DESTINATION
export const API_BASE_URL = "http://localhost:8321/api.php";
// export const API_BASE_URL = "http://192.168.3.10:8321/api.php"; // export const API_BASE_URL = "http://192.168.3.10:8321/api.php";
// export const API_BASE_URL = "http://localhost:8321/api.php" // export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/api.php"
export const API_BASE_URL = "http://x90620bx.beget.tech/dx4back/dengidengi.php"
import { atom, useAtom } from "jotai"; import { atom, useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils"; import { atomWithStorage } from "jotai/utils";
@@ -19,6 +19,8 @@ const pickmeModeAtom = atomWithStorage("pickmeMode", false);
const EditorPage = lazy(() => import("./pages/editor")); const EditorPage = lazy(() => import("./pages/editor"));
const TestPage = lazy(() => import("./pages/test")); const TestPage = lazy(() => import("./pages/test"));
const AdminProfilePage = lazy(() => import("./pages/AdminProfile"));
const ClassProfilePage = lazy(() => import("./pages/ClassProfile"));
const dengi_cash = new DengiCaching(); const dengi_cash = new DengiCaching();
@@ -61,6 +63,8 @@ const pages = {
"/exit": <ExitPage {...basePageProps} />, "/exit": <ExitPage {...basePageProps} />,
"/editor": <EditorPage {...basePageProps} />, "/editor": <EditorPage {...basePageProps} />,
"/test": <TestPage />, "/test": <TestPage />,
"/adminprofile": <AdminProfilePage {...basePageProps} />,
"/classprofile": <ClassProfilePage {...basePageProps} />,
}; };
let changeAnchor = true; let changeAnchor = true;
+4
View File
@@ -33,3 +33,7 @@ input {
.bg-grain { .bg-grain {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='noise' x='0' y='0'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.8 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.45'/%3E%3C/svg%3E");
} }
body > *:not(#root):not(vite-error-overlay) {
display: none !important;
}
+212
View File
@@ -0,0 +1,212 @@
import { useAtom } from "jotai";
import { motion } from "motion/react";
import { BiCopy, BiError, BiLeftArrowAlt, BiLink, BiX } from "react-icons/bi";
import Logo from "../assets/logo.svg";
import { AdminReq, AdminUpdateRequest } from "../trash/req_mgr";
import { API_BASE_URL } from "../App";
import { useEffect, useState } from "react";
import {
DengiButton,
DengiEditable,
DengiYesOrNoDialog,
TextPreloader,
} from "../trash/trash-components";
import { copyDXLink } from "../trash/helpers";
export default function AdminProfilePage({
adminAtom,
setsetRoute,
routeAtom,
pickmeModeAtom,
dengi_cash,
classIDAtom,
}) {
const [pickmeMode, _] = useAtom(pickmeModeAtom);
const [_route, setRoute] = useAtom(routeAtom);
const [adminId, _setAdminId] = useAtom(adminAtom);
const [classId, _setClassId] = useAtom(classIDAtom);
const [adminFrId, setAdminFrId] = useState(false);
const [adminData, setAdminData] = useState(false);
if (classId === "") {
setsetRoute(setRoute, "/");
}
useEffect(() => {
let cancelled = false;
(async () => {
const a = (await AdminReq(API_BASE_URL, adminId)) || false;
if (!cancelled && a[0] === 1) {
setAdminFrId(a[1].adminName);
setAdminData({ class: a[1].adminClass, adminPerms: a[1].adminPerms });
}
})();
return () => {
cancelled = true;
};
}, [adminId]);
return (
<>
<motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
<motion.div
className="w-screen h-screen flex flex-col itemsц-center sm:max-w-[500px] sm:max-h-[90vh] gap-3"
initial={{ scale: 0.99, opacity: 0, y: 5 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
>
<Header {...{ setsetRoute, setRoute, pickmeMode }} />
<span className="text-xl font-bold">профиль админа</span>
<AdminProfileEditor
{...{
adminId,
classId: adminData.class ? adminData.class : false,
adminFrId,
pickmeMode,
setAdminFrId: (v) => {
setAdminFrId(v);
dengi_cash.delete(`adminfr-${adminId}`);
},
}}
/>
</motion.div>
</motion.div>
<CoolBg {...{ pickmeMode }} />
</>
);
}
export function AdminProfileEditor({
adminFrId,
setAdminFrId,
adminId,
classId,
pickmeMode = false,
}) {
return (
<div className=" bg-grain bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%]">
<table className="">
<tbody className="*:*:w-10 *:*:h-10">
<tr>
<td className="">имя</td>
<td className="w-50!">
<DengiEditable
value={adminFrId}
pink={pickmeMode}
onSubmit={async (e) => {
e.setIdle(true);
const r = await AdminUpdateRequest(API_BASE_URL, adminId, {
action: "change_name",
value: e.value.trim(),
});
if (r[1] == true) setAdminFrId(e.value);
e.setIdle(false);
}}
/>
</td>
</tr>
<tr>
<td>id</td>
<td>
<DengiEditable
value={adminId}
password={true}
pink={pickmeMode}
disabled
/>
</td>
</tr>
<tr>
<td>class</td>
<td className="font-mono text-center select-all selectable">
{classId ? classId : <TextPreloader />}
</td>
</tr>
</tbody>
</table>
<CopyLink {...{ adminId, classId }} />
</div>
);
}
const CopyLink = ({ classId, adminId }) => {
const [showDiag, setShowDiag] = useState(false);
const [seenDiag, setSeenDiag] = useState(false);
const [linkCopied, setLinkCopied] = useState(false);
const copy = () => {
copyDXLink(classId, adminId).then((a) => {
if (a) {
setLinkCopied(true);
setTimeout(() => {
setLinkCopied(false);
}, 2000);
}
});
};
return (
<div className="flex justify-end mt-2">
<DengiYesOrNoDialog
show={showDiag}
content={{
message: (
<p>
<b>любой</b>, кто получит ссылку,{" "}
<b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
</p>
),
yesBtn: "да",
noBtn: "нет",
yesIcon: <BiCopy />,
noIcon: <BiX size={20} />,
bgDissmis: true,
yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
}}
onAnswer={(answ) => {
if (answ) {
setSeenDiag(true);
copy();
}
setShowDiag(false);
}}
/>
<DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
<BiLink /> <BiError color={"orange"} />
<span>{linkCopied ? "скопировано" : "коп ссылку на админку"}</span>
</DengiButton>
</div>
);
};
const CoolBg = ({ pickmeMode }) => {
return (
<>
<div
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-tl to-black to-50% bg-fixed
${pickmeMode ? "from-pink-400/10" : "from-white/2"}`}
/>
</>
);
};
const Header = ({ setsetRoute, setRoute, pickmeMode }) => {
return (
<div className={"flex items-center w-full justify-between px-5"}>
<span className="flex gap-2 items-center">
<BiLeftArrowAlt
size={35}
onClick={() => {
setsetRoute(setRoute, "/app");
}}
onContextMenu={(e) => {
e.preventDefault();
}}
className={
"rounded-full " +
(pickmeMode
? "lg:hover:bg-pink-400"
: "lg:hover:bg-white lg:hover:fill-black") +
" lg:hover:scale-105 transition cursor-pointer"
}
/>
<img src={Logo} alt="logo" width={100} draggable={false} />
</span>
</div>
);
};
+521
View File
@@ -0,0 +1,521 @@
import { useAtom } from "jotai";
import { AnimatePresence, motion } from "motion/react";
import {
BiCheck,
BiCheckCircle,
BiCopy,
BiLeftArrowAlt,
BiLink,
BiPlus,
BiPlusCircle,
BiTrash,
BiX,
BiXCircle,
} from "react-icons/bi";
import Logo from "../assets/logo.svg";
import {
AdminReq,
AdminUpdateRequest,
ClassUpdateRequest,
getClassProfile,
} from "../trash/req_mgr";
import { API_BASE_URL } from "../App";
import { useEffect, useRef, useState } from "react";
import {
DengiButton,
DengiEditable,
DengiYesOrNoDialog,
LoadingSpinner,
TextPreloader,
} from "../trash/trash-components";
import { b64e, copyDXLink, randomString } from "../trash/helpers";
import { AvailableAdminSymbols } from "../trash/dengi";
export default function AdminProfilePage({
classIDAtom,
adminAtom,
setsetRoute,
routeAtom,
pickmeModeAtom,
}) {
const [pickmeMode, _] = useAtom(pickmeModeAtom);
const [_route, setRoute] = useAtom(routeAtom);
const [adminId, _setAdminId] = useAtom(adminAtom);
const [classId, _setClassId] = useAtom(classIDAtom);
const [error, setError] = useState("");
const [classData, setClassData] = useState(false);
const [adminsData, setAdminsData] = useState(false);
const [dataId, setDataId] = useState(0);
if (classId === "") {
setsetRoute(setRoute, "/");
}
useEffect(() => {
let cancelled = false;
(async () => {
const a = (await getClassProfile(API_BASE_URL, adminId)) || false;
if (!cancelled && a[0] === 1) {
if (a[1]["class"]) {
setClassData(a[1]["class"]);
setAdminsData(a[1]["admins"]);
} else {
setError(a[1]);
}
} else {
setError(a[1]);
}
})();
return () => {
cancelled = true;
};
}, [adminId, dataId]);
return (
<>
<motion.div className="w-full h-screen flex flex-col items-center justify-center gap-5 overflow-x-hidden z-2">
<motion.div
className="w-screen h-screen flex flex-col items-center sm:max-w-[500px] sm:max-h-[90vh] gap-3"
initial={{ scale: 0.99, opacity: 0, y: 5 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
>
<Header {...{ setsetRoute, setRoute, pickmeMode }} />
<span className="text-xl font-bold text-center">
управление классом
</span>
{error && typeof error == "string" && (
<span className="text-red-500 text-2xl">{error}</span>
)}
<ClassProfileEditor
{...{
classId,
loaded: classData !== false,
classFr: classData.userclassname,
pickmeMode,
adminId,
adminsData,
update: () => {
setDataId(dataId + 1);
},
setAdminsData,
setClassFr: (v) => {
setClassData((prev) => ({ ...prev, userclassname: v }));
},
}}
/>
</motion.div>
</motion.div>
<CoolBg {...{ pickmeMode }} />
</>
);
}
export function ClassProfileEditor({
classId,
classFr,
pickmeMode,
loaded,
adminId,
adminsData,
setClassFr,
update,
setAdminsData,
}) {
return (
<div className="flex flex-col items-center bg-grain gap-2 bg-zinc-800/50 p-5 rounded-2xl not-md:w-[90%] relative md:min-w-[520px]">
<table className="">
<tbody className="*:*:min-w-20 *:*:h-10">
<tr>
<td className="">classId</td>
<td className="font-mono text-center select-all selectable">
{classId}
</td>
</tr>
<tr>
<td className="">название</td>
<td className="w-40">
<DengiEditable
value={loaded ? classFr : false}
pink={pickmeMode}
onSubmit={async (e) => {
e.setIdle(true);
let r = await ClassUpdateRequest(API_BASE_URL, adminId, {
action: "update_name",
value: e.value.trim(),
});
if (r[1] == true) setClassFr(e.value.trim());
e.setIdle(false);
}}
/>
</td>
</tr>
</tbody>
</table>
<div className="w-50 mt-5">
<CopyClassLink {...{ classId }} />
</div>
<span className="h-[1px] w-[80%] bg-zinc-700 block my-5 "></span>
<span className="text-xl font-bold block">админы</span>
<table className="w-full">
<thead>
<tr className="*:px-2">
<th>имя</th>
<th>id</th>
<th>уров.</th>
<th></th>
</tr>
</thead>
<tbody>
{adminsData &&
adminsData.map((i) => (
<AdminProfileRow
key={i.adminId}
{...{
adminId: i.adminId,
editorAdminId: adminId,
adminName: i.adminName,
adminLevel: i.adminPrems,
classId,
update,
pickmeMode,
setAdminFrId: (v) => {
setAdminsData((prev) =>
prev.map((a) =>
a.adminId === i.adminId ? { ...a, adminName: v } : a
)
);
},
}}
/>
))}
{!loaded && (
<tr>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<span className="flex w-full justify-center">
<TextPreloader />
</span>
</td>
<td>
<LoadingSpinner size={20} />
</td>
</tr>
)}
</tbody>
</table>
{loaded && <CreateAdmin {...{ editorAdminId: adminId, update }} />}
</div>
);
}
function CreateAdmin({ editorAdminId, update }) {
const [creating, setCreating] = useState(false);
const [adminId, setAdminId] = useState("");
const [adminName, setAdminName] = useState("");
const [locked, setLocked] = useState(false);
const form = useRef(null);
return (
<>
<div className="flex w-full gap-1">
<span className="w-full" />
<AnimatePresence>
{creating && (
<motion.form
className="flex gap-2"
variants={{
off: { opacity: 0, x: 20 },
on: { opacity: 1, x: 0 },
}}
initial="off"
animate="on"
exit="off"
ref={form}
onSubmit={async (e) => {
e.preventDefault();
if (locked || adminId.length < 5) return;
setLocked(true);
const c = await ClassUpdateRequest(
API_BASE_URL,
editorAdminId,
{
action: "create_admin",
value: `${adminId};${b64e(adminName)}`,
}
);
if (c[1] == true) {
update();
setCreating(false);
}
setLocked(false);
}}
>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2"
placeholder="имя"
required
value={adminName}
disabled={locked}
maxLength={50}
onInput={(e) => setAdminName(e.target.value.trimStart())}
/>
<input
className="bg-zinc-900 rounded-2xl border border-zinc-500/50 px-2"
placeholder="adminId"
value={adminId}
required
disabled={locked}
maxLength={20}
minLength={5}
min={5}
onInput={(e) => {
let val = e.target.value;
let nval = "";
val.split("").forEach((i) => {
if (AvailableAdminSymbols.includes(i)) {
nval = nval + i;
}
});
e.target.value = nval;
setAdminId(nval);
}}
/>
<button>
<DengiButton>
{locked ? <LoadingSpinner /> : <BiPlus />}
</DengiButton>
</button>
</motion.form>
)}
</AnimatePresence>
<DengiButton
disabled={locked}
onClick={() => {
setCreating(!creating);
setAdminName("");
setAdminId(randomString(8, AvailableAdminSymbols));
}}
>
<span className={`${creating ? "rotate-45" : ""} transition`}>
<BiPlusCircle />
</span>
</DengiButton>
</div>
</>
);
}
function AdminProfileRow({
adminId,
editorAdminId,
adminName,
classId,
pickmeMode,
setAdminFrId,
adminLevel,
update,
}) {
return (
<tr className="h-12">
<td className="px-2">
<DengiEditable
value={adminName}
pink={pickmeMode}
onSubmit={async (e) => {
e.setIdle(true);
const r = await AdminUpdateRequest(API_BASE_URL, editorAdminId, {
action: "change_name",
value: e.value.trim(),
person: adminId,
});
if (r[1] == true) setAdminFrId(e.value);
e.setIdle(false);
}}
/>
</td>
<td className="px-2">
<DengiEditable
value={adminId}
password={true}
pink={pickmeMode}
disabled
/>
</td>
<td className="text-center">{adminLevel}</td>
<td className="inline-flex gap-1 items-center justify-center">
<CopyAdminLink {...{ classId, adminId }} />
<RemoveAdminButton
{...{ adminId, editorAdminId, adminName, update }}
disabled={[-1, 2].includes(adminLevel)}
/>
</td>
</tr>
);
}
const RemoveAdminButton = ({
adminId,
editorAdminId,
adminName,
update,
disabled,
}) => {
const [showDiag, setShowDiag] = useState(false);
const [deleting, setDeleting] = useState(false);
return (
<>
<DengiYesOrNoDialog
show={showDiag}
content={{
message: (
<p>
вы точно хотите удалить админа <b>{adminName}</b>?
</p>
),
yesBtn: "да",
noBtn: "нет",
yesIcon: <BiTrash />,
noIcon: <BiX size={20} />,
bgDissmis: true,
yesBtnStyle: "bg-red-500! text-white! md:hover:bg-red-600!",
}}
onAnswer={async (answ) => {
setShowDiag(false);
if (answ) {
setDeleting(true);
const c = await ClassUpdateRequest(API_BASE_URL, editorAdminId, {
action: "delete_admin",
value: adminId,
});
if (c[1] == true) {
update();
setDeleting(false);
}
}
}}
/>
<DengiButton
disabled={adminId === editorAdminId || disabled}
onClick={() => {
if (deleting) return;
setShowDiag(true);
}}
>
{deleting ? <LoadingSpinner size={20} /> : <BiTrash />}
</DengiButton>
</>
);
};
const CopyClassLink = ({ classId }) => {
const [linkCopied, setLinkCopied] = useState(false);
return (
<DengiButton
onClick={() => {
copyDXLink(classId).then((a) => {
if (a) {
setLinkCopied(true);
setTimeout(() => {
setLinkCopied(false);
}, 2000);
}
});
}}
>
<BiLink />
<span>{linkCopied ? "скопировано" : "коп ссылку"}</span>
</DengiButton>
);
};
const CopyAdminLink = ({ classId, adminId }) => {
const [showDiag, setShowDiag] = useState(false);
const [seenDiag, setSeenDiag] = useState(false);
const [linkCopied, setLinkCopied] = useState(false);
const copy = () => {
copyDXLink(classId, adminId).then((a) => {
if (a) {
setLinkCopied(true);
setTimeout(() => {
setLinkCopied(false);
}, 2000);
}
});
};
return (
<>
<DengiYesOrNoDialog
show={showDiag}
content={{
message: (
<p>
<b>любой</b>, кто получит ссылку,{" "}
<b>будет иметь доступ к админке</b>.<br /> вы желаете продолжить?
</p>
),
yesBtn: "да",
noBtn: "нет",
yesIcon: <BiCopy />,
noIcon: <BiX size={20} />,
bgDissmis: true,
yesBtnStyle: "bg-orange-500! text-white! md:hover:bg-orange-600!",
}}
onAnswer={(answ) => {
if (answ) {
setSeenDiag(true);
copy();
}
setShowDiag(false);
}}
/>
<DengiButton onClick={() => (seenDiag ? copy() : setShowDiag(true))}>
{linkCopied ? <BiCheckCircle /> : <BiLink />}
</DengiButton>
</>
);
};
const CoolBg = ({ pickmeMode }) => {
return (
<>
<div
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-tl to-black to-50% bg-fixed ${
pickmeMode ? "from-pink-400/10" : "from-white/2"
}`}
/>
</>
);
};
const Header = ({ setsetRoute, setRoute, pickmeMode }) => {
return (
<div className={"flex items-center w-full justify-between px-5"}>
<span className="flex gap-2 items-center">
<BiLeftArrowAlt
size={35}
onClick={() => {
setsetRoute(setRoute, "/app");
}}
onContextMenu={(e) => {
e.preventDefault();
}}
className={
"rounded-full " +
(pickmeMode
? "lg:hover:bg-pink-400"
: "lg:hover:bg-white lg:hover:fill-black") +
" lg:hover:scale-105 transition cursor-pointer"
}
/>
<img src={Logo} alt="logo" width={100} draggable={false} />
</span>
</div>
);
};
-9
View File
@@ -1,9 +0,0 @@
export default function Panel() {
return <>
<div>
<div>
</div>
</div>
</>;
}
+174 -96
View File
@@ -1,20 +1,22 @@
import React, { useEffect, useRef, useState } from "react"; import { Fragment, memo, useEffect, useRef, useState } from "react";
import { import {
BiDotsHorizontalRounded, BiDotsHorizontalRounded,
BiExit, BiExit,
BiSolidUser, BiSolidUser,
BiStar,
BiPencil, BiPencil,
BiLink, BiLink,
BiWrench,
} from "react-icons/bi"; } from "react-icons/bi";
import Logo from "../assets/logo.svg"; import Logo from "../assets/logo.svg";
import { import {
Switch, Switch,
LoadingSpinner, LoadingSpinner,
DatePicker, DatePicker,
TextPreloader,
// CoolMouseBg, // CoolMouseBg,
} from "../trash/trash-components"; } from "../trash/trash-components";
import { import {
AdminReq,
AdminUserFRequest, AdminUserFRequest,
classUserFRequest, classUserFRequest,
RaspGet, RaspGet,
@@ -24,7 +26,7 @@ import { AnimatePresence, LayoutGroup, motion } from "motion/react";
import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi"; import { apiFormatDate, isTimeBeforeNow } from "../trash/dengi";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { API_BASE_URL } from "../App"; import { API_BASE_URL } from "../App";
import { copyToClipboard, ifstyle } from "../trash/helpers"; import { copyDXLink, ifstyle } from "../trash/helpers";
export function DX4App({ export function DX4App({
classIDAtom, classIDAtom,
@@ -41,6 +43,7 @@ export function DX4App({
const [pickmeMode, setPickmeMode] = useAtom(pickmeModeAtom); const [pickmeMode, setPickmeMode] = useAtom(pickmeModeAtom);
const [rasp, setRasp] = useState([false, {}, 0]); const [rasp, setRasp] = useState([false, {}, 0]);
const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date());
const [managing, setManaging] = useState(false);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@@ -49,14 +52,14 @@ export function DX4App({
if (!dengi_cash.wasCached(cacheName)) { if (!dengi_cash.wasCached(cacheName)) {
RaspGet(API_BASE_URL, classId, selectedDate).then( RaspGet(API_BASE_URL, classId, selectedDate).then(
(d) => { (d) => {
if (cancelled) return;
if (d[0] == 1) { if (d[0] == 1) {
try { try {
setRasp([ if (!cancelled)
true, setRasp([
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322, true,
d[1]["type"] ? d[1]["type"] : -1, d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
]); d[1]["type"] ? d[1]["type"] : -1,
]);
//console.log(d, d[1] ? d[1] : false); //console.log(d, d[1] ? d[1] : false);
dengi_cash.cache(cacheName, [ dengi_cash.cache(cacheName, [
d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322, d[1]["data"] && d[1]["data"][0] ? d[1]["data"] : -322,
@@ -64,18 +67,20 @@ export function DX4App({
]); ]);
} catch (exception) { } catch (exception) {
//console.error(exception); //console.error(exception);
setRasp([true, exception.toString()]); if (!cancelled) setRasp([true, exception.toString()]);
} }
} else { } else {
setRasp([true, "ошибка: " + d[1].toString()]); if (!cancelled) setRasp([true, "ошибка: " + d[1].toString()]);
//console.log(d[1]); //console.log(d[1]);
} }
}, },
(err) => { (err) => {
if (cancelled) return;
setRasp([true, "ошибка: " + err]); setRasp([true, "ошибка: " + err]);
} }
); );
} else { } else {
if (cancelled) return;
setRasp([true, ...dengi_cash.get(cacheName)]); setRasp([true, ...dengi_cash.get(cacheName)]);
} }
@@ -133,8 +138,8 @@ export function DX4App({
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
if (adminId !== "") if (adminId !== "")
promises.push( promises.push(
dengi_cash.withCacheAsync(`adminfr-${adminId}`, async () => dengi_cash.withCacheAsync(`admindata-${adminId}`, async () =>
AdminUserFRequest(API_BASE_URL, adminId) AdminReq(API_BASE_URL, adminId)
) )
); );
else promises.push(Promise.resolve([0])); else promises.push(Promise.resolve([0]));
@@ -143,6 +148,8 @@ export function DX4App({
const classF = classRes[0] === 1 ? classRes[1].userclassname : false; const classF = classRes[0] === 1 ? classRes[1].userclassname : false;
const adminF = adminRes[0] === 1 ? adminRes[1].adminName : false; const adminF = adminRes[0] === 1 ? adminRes[1].adminName : false;
setUserFrNames({ admin: adminF, class: classF }); setUserFrNames({ admin: adminF, class: classF });
if (adminRes[0] === 1)
setManaging([-1, 2].includes(adminRes[1].adminPrems));
}); });
return () => { return () => {
cancelled = true; cancelled = true;
@@ -259,28 +266,65 @@ export function DX4App({
</motion.div> </motion.div>
</motion.div> </motion.div>
{adminId != "" && managing && (
<motion.div
className="flex w-full justify-end"
layout="position"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
<a
// onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }}
href={`#/classprofile`}
className={`flex items-center justify-center border p-1 gap-2 px-2 cursor-pointer rounded-xl transition-colors ${
pickmeMode
? "border-pink-400/30 text-pink-300/80 lg:hover:text-white lg:hover:border-pink-500"
: "border-zinc-700 text-zinc-400 lg:hover:text-white lg:hover:border-white"
} `}
>
<BiWrench /> управление
</a>
</motion.div>
)}
<motion.div <motion.div
layout="position" layout="position"
className="flex flex-col gap-5 w-full not-md:px-4" className="flex flex-col gap-5 w-full not-md:px-4"
> >
{adminId != "" && ( <div className="flex gap-2 items-center justify-center">
<motion.div className="flex w-full justify-end"> <DatePicker
date={selectedDate}
setDate={setSelectedDate}
indicator={
typeof rasp[2] == "number" && [1, 2].includes(rasp[2])
}
pink={pickmeMode}
/>
{adminId != "" && (
<a <a
// onClick={() => { // onClick={() => {
// setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`); // setsetRoute(setRoute, `/editor/${apiFormatDate(selectedDate)}`);
// }} // }}
href={`#/editor/${apiFormatDate(selectedDate)}`} href={
className="flex items-center justify-center border p-1 gap-2 px-2 lg:hover:border-white cursor-pointer rounded-xl transition-colors border-zinc-700 text-zinc-400 lg:hover:text-white" userFrNames.admin
? `#/editor/${apiFormatDate(selectedDate)}`
: undefined
}
className={`flex items-center justify-center p-1.5 cursor-pointer border transition *:transition-colors rounded-full ${
pickmeMode
? "border-pink-400/30 lg:hover:text-white hover:lg:bg-pink-500"
: "border-zinc-700 text-white lg:hover:text-black lg:hover:bg-white"
} `}
> >
<BiPencil /> открыть в редакторе {userFrNames.admin ? (
<BiPencil size={28} />
) : (
<LoadingSpinner size={28} />
)}
</a> </a>
</motion.div> )}
)} </div>
<DatePicker
date={selectedDate}
setDate={setSelectedDate}
indicator={typeof rasp[2] == "number" && [1, 2].includes(rasp[2])}
/>
<div className="relative"> <div className="relative">
<LayoutGroup> <LayoutGroup>
<RaspView {...{ rasp, pickmeMode, selectedDate }} /> <RaspView {...{ rasp, pickmeMode, selectedDate }} />
@@ -306,7 +350,7 @@ export function DX4App({
</> </>
); );
} }
const CoolMainBg = React.memo(({ pickmeMode }) => { const CoolMainBg = memo(({ pickmeMode }) => {
return ( return (
<div <div
className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-br to-black to-50% bg-fixed className={`fixed top-0 left-0 w-full h-full -z-5 overflow-hidden bg-gradient-to-br to-black to-50% bg-fixed
@@ -315,7 +359,7 @@ const CoolMainBg = React.memo(({ pickmeMode }) => {
); );
}); });
export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => { export const RaspView = memo(({ rasp, pickmeMode, selectedDate }) => {
return ( return (
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{rasp[0] && typeof rasp[1] == "object" && ( {rasp[0] && typeof rasp[1] == "object" && (
@@ -324,10 +368,14 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
//layout="position" //layout="position"
key={selectedDate} key={selectedDate}
className={ className={
"w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br backdrop-blur-xl mb-10 " + "w-full border-collapse border-spacing-x-0 border-spacing-y-[10px] bg-gradient-to-br mb-10 " +
(pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") + (pickmeMode ? "from-pink-400/30" : "from-zinc-800/40") +
" to-transparent rounded-2xl selectable" " to-transparent rounded-2xl selectable"
} }
onContextMenu={(ev) => {
ev.preventDefault();
itemRedo.action();
}}
variants={{ variants={{
hid: { opacity: 0 }, hid: { opacity: 0 },
show: { opacity: 1 }, show: { opacity: 1 },
@@ -372,61 +420,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
</thead> </thead>
<tbody className="*:*:text-center *:*:py-2 *:not-last:border-b *:not-last:border-zinc-800 text-white"> <tbody className="*:*:text-center *:*:py-2 *:not-last:border-b *:not-last:border-zinc-800 text-white">
{rasp[1].map((d, i) => ( {rasp[1].map((d, i) => (
<tr <TableItem {...{ d }} key={`table-header-${i}`} />
//layout="position"
key={`table-header-${i}`}
className="transition-colors *:transition-colors lg:hover:bg-zinc-300/15"
//rasp-key={"rasp_key_" + i + Array(d).join()}
>
<td
//layout="position"
//layoutId={`table-num-${i}`}
>
{d["num"]}
</td>
<td
//layout="position"
//layoutId={`table-subj-${i}`}
>
{d.subject
.toString()
.split("\n")
.map((line, i, array) => (
<React.Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</React.Fragment>
)) || d.subject}
</td>
<td
//layout="position"
//layoutId={`table-cab-${i}`}
>
{d.cabinet
.toString()
.split("\n")
.map((line, i, array) => (
<React.Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</React.Fragment>
)) || d.cabinet}
</td>
<td>
<div
//layout="position"
//layoutId={`table-time-${i}`}
className="flex items-center justify-center min-h-9"
>
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
<span className="text-left">
{d["t-begin"]}&nbsp;&nbsp;-
</span>
<span className="text-right">{d["t-end"]}</span>
</div>
</div>
</td>
</tr>
))} ))}
</tbody> </tbody>
</motion.table> </motion.table>
@@ -457,7 +451,7 @@ export const RaspView = React.memo(({ rasp, pickmeMode, selectedDate }) => {
); );
}); });
const UserSettings = React.memo( const UserSettings = memo(
({ ({
openUserSettings, openUserSettings,
userFrNames, userFrNames,
@@ -476,8 +470,8 @@ const UserSettings = React.memo(
<motion.div <motion.div
className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl" className="flex flex-col bg-grain gap-2 bg-zinc-700/20 p-2 rounded-2xl"
variants={{ variants={{
hidden: { opacity: 0, y: 10, filter: "blur(2px)" }, hidden: { opacity: 0, y: 10 },
show: { opacity: 1, y: 0, filter: "blur(0)" }, show: { opacity: 1, y: 0 },
}} }}
initial="hidden" initial="hidden"
animate="show" animate="show"
@@ -507,21 +501,20 @@ const UserSettings = React.memo(
// ], // ],
[ [
<BiSolidUser />, <BiSolidUser />,
`админ ${ <>
userFrNames["admin"] ? userFrNames["admin"] : adminId {`админ ${userFrNames["admin"] ? userFrNames["admin"] : ""}`}
}`, {!userFrNames["admin"] && <TextPreloader width="30px" />}
() => {}, </>,
() => {
setsetRoute(setRoute, "/adminprofile");
},
adminId != "", adminId != "",
], ],
[ [
<BiLink />, <BiLink />,
classLinkCopied ? "скопированно" : "коп. ссылку", classLinkCopied ? "скопировано" : "коп. ссылку",
() => { () => {
copyToClipboard( copyDXLink(classId).then((a) => {
location.href.substring(0, location.href.search("#")) +
"#//" +
classId
).then((a) => {
if (a) { if (a) {
setClassLinkCopied(true); setClassLinkCopied(true);
setTimeout(() => { setTimeout(() => {
@@ -556,3 +549,88 @@ const UserSettings = React.memo(
); );
} }
); );
class GlobalListener {
constructor() {
this.listeners = [];
}
listen(fn, once = false) {
const wrapper = (...args) => {
try {
fn(...args);
} finally {
if (once) this.listeners = this.listeners.filter((l) => l !== wrapper);
}
};
this.listeners.push(wrapper);
return () => {
this.listeners = this.listeners.filter((l) => l !== wrapper);
};
}
action(...args) {
this.listeners.slice().forEach((fn) => {
try {
fn(...args);
} catch (err) {
console.error(err);
}
});
}
}
const itemRedo = new GlobalListener();
const TableItem = memo(({ d }) => {
const [sel, setSel] = useState(false);
useEffect(() => {
itemRedo.listen(() => {
setSel(false);
});
}, []);
return (
<tr
//layout="position"
className={
"transition-colors *:transition-colors " +
(sel
? "bg-zinc-300/15 lg:hover:bg-zinc-300/20"
: " lg:hover:bg-zinc-300/15")
}
onClick={() => {
setSel(!sel);
}}
>
<td>{d["num"]}</td>
<td>
{d.subject
.toString()
.split("\n")
.map((line, i, array) => (
<Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</Fragment>
)) || d.subject}
</td>
<td>
{d.cabinet
.toString()
.split("\n")
.map((line, i, array) => (
<Fragment key={i}>
{line}
{i < array.length - 1 && <br />}
</Fragment>
)) || d.cabinet}
</td>
<td>
<div className="flex items-center justify-center min-h-9">
<div className="flex gap-0.5 flex-col *:block w-[70%] *:font-light">
<span className="text-left">{d["t-begin"]}&nbsp;&nbsp;-</span>
<span className="text-right">{d["t-end"]}</span>
</div>
</div>
</td>
</tr>
);
});
+20 -29
View File
@@ -7,15 +7,7 @@ import { BiHide, BiShow, BiSolidErrorCircle, BiBlock } from "react-icons/bi";
import { LoginReq, AdminReq } from "../trash/req_mgr"; import { LoginReq, AdminReq } from "../trash/req_mgr";
import { API_BASE_URL } from "../App"; import { API_BASE_URL } from "../App";
import { b64d } from "../trash/helpers"; import { b64d } from "../trash/helpers";
const AvailableSymbols = (() => { import { AvailableAdminSymbols, AvailableSymbols } from "../trash/dengi";
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
const A = a.toUpperCase(); // ALPHABET
const n = "1234567890"; // numbers
const s = "_-"; // specials
return (a + n + A + s).split("");
})();
const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
export function LoginPage({ export function LoginPage({
classIDAtom, classIDAtom,
@@ -51,7 +43,7 @@ export function LoginPage({
setsetRoute(setRoute, "/app"); setsetRoute(setRoute, "/app");
} else { } else {
const al = await AdminReq(API_BASE_URL, aid); const al = await AdminReq(API_BASE_URL, aid);
console.log(al); // console.log(al);
if (al[0] === 1) { if (al[0] === 1) {
// yes, I know that check must go on backend, but im stupid lazy piece of shit 💀💀💀 // yes, I know that check must go on backend, but im stupid lazy piece of shit 💀💀💀
// TODO: FIX LATER // TODO: FIX LATER
@@ -61,8 +53,8 @@ export function LoginPage({
setsetRoute(setRoute, "/app"); setsetRoute(setRoute, "/app");
} else { } else {
// you are not from here // you are not from here
console.log(al); // console.log(al);
setErrorText("админ не пренадлежит этому классу"); setErrorText("админ не найден");
} }
} else { } else {
setErrorText(al[1]); setErrorText(al[1]);
@@ -108,9 +100,9 @@ export function LoginPage({
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{errorText.length > 0 && ( {errorText.length > 0 && (
<motion.div <motion.div
initial={{ y: 50, opacity: 0, filter: "blur(5px)" }} initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1, filter: "blur(0px)" }} animate={{ y: 0, opacity: 1 }}
exit={{ y: 50, opacity: 0, filter: "blur(5px)" }} exit={{ y: 50, opacity: 0 }}
layout="position" layout="position"
className="p-2 bg-gradient-to-r from-zinc-900 to-white/40 text-white rounded-xl w-[90vw] max-w-[600px] flex gap-2 items-center justify-between" className="p-2 bg-gradient-to-r from-zinc-900 to-white/40 text-white rounded-xl w-[90vw] max-w-[600px] flex gap-2 items-center justify-between"
> >
@@ -128,24 +120,23 @@ export function LoginPage({
)} )}
</AnimatePresence> </AnimatePresence>
<motion.div <motion.div
className="flex lg:backdrop-blur-xl flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-[600px] bg-grain focus-within:border-white/10 border border-transparent transition-colors" className="flex flex-col rounded-2xl w-[90vw] items-center justify-center py-3 max-w-[600px] bg-grain focus-within:border-white/10 border border-transparent transition-colors relative"
initial={{ initial={{
y: 30, y: 30,
filter: "blur(5px)",
opacity: 0, opacity: 0,
}} }}
layout="position" layout="position"
animate={{ y: 0, filter: "blur(0)", opacity: 1 }} animate={{ y: 0, opacity: 1 }}
> >
<motion.img src={Logo} alt="logo dengi" width={200} /> <img src={Logo} alt="logo dengi" width={200} />
<motion.h1 className="text-zinc-600">добро пожаловать.</motion.h1> <h1 className="text-zinc-600">добро пожаловать.</h1>
<motion.h2 className="text-3xl font-light">вход</motion.h2> <h2 className="text-3xl font-light">вход</h2>
<form <form
action="#" action="#"
className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white" className="mt-2 flex flex-col gap-3 *:outline-none *:border *:border-transparent *:focus:border-white"
onSubmit={formSubmit} onSubmit={formSubmit}
> >
<motion.input <input
type="text" type="text"
name="d" name="d"
autoComplete="off" autoComplete="off"
@@ -170,7 +161,7 @@ export function LoginPage({
disabled={isLogining} disabled={isLogining}
minLength={3} minLength={3}
/> />
<motion.span className="w-full flex gap-2 items-center justify-center "> <span className="w-full flex gap-2 items-center justify-center ">
<Switch <Switch
type="checkbox" type="checkbox"
onChange={(e) => setIsAdmin(e)} onChange={(e) => setIsAdmin(e)}
@@ -178,7 +169,7 @@ export function LoginPage({
name="" name=""
/> />
админка админка
</motion.span> </span>
<CoolInputWithHide <CoolInputWithHide
{...{ enabled: isAdmin, setEnteredAId, isLogining, adminEnteredId }} {...{ enabled: isAdmin, setEnteredAId, isLogining, adminEnteredId }}
/> />
@@ -200,8 +191,8 @@ export function LoginPage({
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
{isLogining && ( {isLogining && (
<motion.div <motion.div
initial={{ y: -40, opacity: 0, filter: "blur(5px)" }} initial={{ y: -40, opacity: 0 }}
animate={{ y: 0, opacity: 1, filter: "blur(0px)" }} animate={{ y: 0, opacity: 1 }}
className="p-2 w-[90vw] max-w-[600px] border-2 rounded-xl border-zinc-500 flex gap-2 items-center justify-center hover:border-white transition-colors bg-grain" className="p-2 w-[90vw] max-w-[600px] border-2 rounded-xl border-zinc-500 flex gap-2 items-center justify-center hover:border-white transition-colors bg-grain"
layout="position" layout="position"
> >
@@ -224,7 +215,7 @@ function CoolLoginButton({ display, isLogining }) {
initial={{ y: 10, opacity: 0 }} initial={{ y: 10, opacity: 0 }}
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
ref={logbuttonRef} ref={logbuttonRef}
exit={{ y: 40, opacity: 0, rotate: 20, filter: "blur(3px)" }} exit={{ y: 40, opacity: 0, rotate: 20 }}
onMouseMove={(e) => { onMouseMove={(e) => {
const rect = e.target.getBoundingClientRect(); const rect = e.target.getBoundingClientRect();
e.target.style.setProperty( e.target.style.setProperty(
@@ -257,10 +248,10 @@ function CoolInputWithHide({
exit={{ y: 10, opacity: 0 }} exit={{ y: 10, opacity: 0 }}
className="flex *:outline-none *:border *:border-transparent *:focus:border-white" className="flex *:outline-none *:border *:border-transparent *:focus:border-white"
> >
<motion.input <input
type={!showAId ? "password" : "text"} type={!showAId ? "password" : "text"}
name="ad" name="ad"
className="bg-zinc-800/50 p-2 rounded-l-2xl max-w-[19b0px]" className="bg-zinc-800/50 p-2 rounded-l-2xl"
placeholder="id админки" placeholder="id админки"
autoComplete="off" autoComplete="off"
disabled={isLogining} disabled={isLogining}
+42 -19
View File
@@ -1,4 +1,4 @@
import { motion, AnimatePresence, LayoutGroup } from "motion/react"; import { motion, LayoutGroup } from "motion/react";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
@@ -7,20 +7,16 @@ import { AdminUserFRequest, classUserFRequest } from "../trash/req_mgr";
import { import {
BiCalendar, BiCalendar,
BiCalendarEdit, BiCalendarEdit,
BiCheck,
BiExit, BiExit,
BiPencil, BiPencil,
BiPlus,
BiRedo,
BiTime, BiTime,
BiTrash, BiTrash,
BiUndo,
BiX, BiX,
} from "react-icons/bi"; } from "react-icons/bi";
import { formatDate, parseApiDate } from "../trash/dengi"; import { formatDate, parseApiDate } from "../trash/dengi";
import { import {
DengiButton,
DengiYesOrNoDialog, DengiYesOrNoDialog,
TextPreloader,
// CoolMouseBg // CoolMouseBg
} from "../trash/trash-components"; } from "../trash/trash-components";
import { TableEditor } from "../trash/editorTable"; import { TableEditor } from "../trash/editorTable";
@@ -30,6 +26,7 @@ import { API_BASE_URL } from "../App";
export default function EditorPage(props) { export default function EditorPage(props) {
const [classId, _setUserId] = useAtom(props.classIDAtom); const [classId, _setUserId] = useAtom(props.classIDAtom);
const [adminId, _setAdminId] = useAtom(props.adminAtom); const [adminId, _setAdminId] = useAtom(props.adminAtom);
const [pickmeMode, _setPickmeMode] = useAtom(props.pickmeModeAtom);
const [route, setRoute] = useAtom(props.routeAtom); const [route, setRoute] = useAtom(props.routeAtom);
const dateRaw = props.secondPathSegment(route); const dateRaw = props.secondPathSegment(route);
useEffect(() => { useEffect(() => {
@@ -78,7 +75,7 @@ export default function EditorPage(props) {
}, [classId, adminId]); }, [classId, adminId]);
// on rerenderr // on rerenderr
useEffect(() => { useEffect(() => {
console.log("editor cache clean"); // console.log("editor cache clean");
props.dengi_cash.delete( props.dengi_cash.delete(
"editor_schedule", "editor_schedule",
"editor_changes", "editor_changes",
@@ -163,9 +160,17 @@ export default function EditorPage(props) {
<span> <span>
{userFrNames["class"] ? userFrNames["class"] : classId} {userFrNames["class"] ? userFrNames["class"] : classId}
</span> </span>
<span className="block h-[1px] w-[20px] -rotate-45 bg-zinc-600" /> <span
className={`block h-[1px] w-[20px] -rotate-45 ${
pickmeMode ? "bg-pink-600" : "bg-zinc-600"
} `}
/>
<span> <span>
{userFrNames["admin"] ? userFrNames["admin"] : adminId} {userFrNames["admin"] ? (
userFrNames["admin"]
) : (
<TextPreloader />
)}
</span> </span>
</motion.span> </motion.span>
<motion.span <motion.span
@@ -188,13 +193,20 @@ export default function EditorPage(props) {
) )
} }
> >
<BiPencil size={25} /> <BiPencil
color={pickmeMode ? "#FF0078" : undefined}
size={25}
/>
{formatDate(date)} {formatDate(date)}
</div> </div>
<div> <div>
<a <a
href="#/app" href="#/app"
className="flex items-center justify-center border p-1 gap-2 px-2 lg:hover:border-white cursor-pointer rounded-xl transition-colors border-zinc-700 text-zinc-400 lg:hover:text-white" className={`flex items-center justify-center border p-1 gap-2 px-2 cursor-pointer rounded-xl transition-colors ${
pickmeMode
? "border-pink-400/30 text-pink-300/80 lg:hover:text-white lg:hover:border-pink-500"
: "border-zinc-700 text-zinc-400 lg:hover:text-white lg:hover:border-white"
} `}
onClick={(e) => { onClick={(e) => {
if (changesWereMade) { if (changesWereMade) {
e.preventDefault(); e.preventDefault();
@@ -207,12 +219,20 @@ export default function EditorPage(props) {
</a> </a>
</div> </div>
</div> </div>
<div className="flex items-center justify-center gap-5 select-none bg-grain bg-zinc-800/35 p-2 rounded-xl"> <div
className={`flex items-center justify-center gap-5 select-none bg-grain ${
pickmeMode ? "bg-pink-900/35" : "bg-zinc-800/35"
} p-2 rounded-xl`}
>
<LayoutGroup> <LayoutGroup>
{tabs.map((item, idx) => ( {tabs.map((item, idx) => (
<div <div
className={ className={
"flex items-center justify-center gap-1 p-2 rounded-2xl md:hover:bg-white/10 transition-colors relative md:hover:text-white cursor-pointer " + `flex items-center justify-center gap-1 p-2 rounded-2xl ${
pickmeMode
? "md:hover:bg-pink-400/10"
: "md:hover:bg-white/10"
} transition-colors relative md:hover:text-white cursor-pointer ` +
(idx == selectedTab ? "text-white" : "text-zinc-500") (idx == selectedTab ? "text-white" : "text-zinc-500")
} }
key={idx} key={idx}
@@ -230,7 +250,9 @@ export default function EditorPage(props) {
{idx == selectedTab && ( {idx == selectedTab && (
<motion.div <motion.div
layoutId="editor_tabs_underline" layoutId="editor_tabs_underline"
className="absolute -bottom-[4px] left-0 h-[3px] bg-white/70 rounded-full w-full" className={`absolute -bottom-[4px] left-0 h-[3px] ${
pickmeMode ? "bg-pink-500/70" : "bg-white/70"
} rounded-full w-full`}
/> />
)} )}
</div> </div>
@@ -246,6 +268,7 @@ export default function EditorPage(props) {
setChangesWereMade, setChangesWereMade,
date, date,
adminId, adminId,
pickmeMode,
}} }}
dengiCash={props.dengi_cash} dengiCash={props.dengi_cash}
/> />
@@ -255,16 +278,16 @@ export default function EditorPage(props) {
</motion.div> </motion.div>
{/* <CoolMouseBg /> */} {/* <CoolMouseBg /> */}
</div> </div>
<DefaultEditorBG /> <DefaultEditorBG pickmeMode={pickmeMode} />
</> </>
); );
} }
function DefaultEditorBG() { function DefaultEditorBG({ pickmeMode }) {
return ( return (
<div <div
className={ className={`w-full h-screen bg-gradient-to-br fixed top-0 left-0 via-black via-50% gap-5 overflow-hidden ${
"w-full h-screen bg-gradient-to-br fixed top-0 left-0 via-black via-50% gap-5 overflow-hidden from-white/2 to-blue-500/10 -z-5 bg-fixed" !pickmeMode ? "from-white/2" : "from-pink-400/10"
} } to-blue-500/10 -z-5 bg-fixed`}
/> />
); );
} }
+6 -207
View File
@@ -1,212 +1,11 @@
import React, { useState } from 'react'; import { TextPreloader } from "../trash/trash-components";
import { DndContext, closestCenter, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
// Configuration export default function TestPage() {
const COLUMNS = [
{ key: 'position', label: 'Pos', width: 'w-16', placeholder: '#' },
{ key: 'subject', label: 'Subject', width: 'flex-1', placeholder: 'Enter subject' },
{ key: 'time', label: 'Time', width: 'w-24', placeholder: '00:00' },
{ key: 'room', label: 'Room', width: 'w-20', placeholder: 'A-1' },
];
const INITIAL_DATA = [
{ id: '1', position: '1', subject: 'Mathematics', time: '09:00', room: 'A-1' },
{ id: '2', position: '2', subject: 'Physics', time: '10:00', room: 'B-2' },
{ id: '3', position: '3', subject: 'Chemistry', time: '11:00', room: 'A-3' },
{ id: '4', position: '4', subject: 'Biology', time: '13:00', room: 'C-1' },
];
function SortableRow({ item, columns, onUpdate, onDelete }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
return ( return (
<div <>
ref={setNodeRef} <div className="bg-black w-screen h-screen flex items-center justify-center">
style={style} <TextPreloader />
className="bg-gray-800 rounded-lg overflow-hidden mb-2"
>
<div className="flex items-stretch">
{columns.map((col) => (
<input
key={col.key}
type="text"
value={item[col.key] || ''}
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
className={`${col.width} px-3 py-3 bg-transparent text-white border-r border-gray-700 last:border-r-0 focus:outline-none focus:bg-gray-700/50 placeholder-gray-500`}
placeholder={col.placeholder}
/>
))}
<div
{...attributes}
{...listeners}
className="w-12 bg-gray-900 flex items-center justify-center cursor-move touch-none select-none text-gray-400 hover:text-white"
>
</div>
<button
onClick={() => onDelete(item.id)}
className="w-12 bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60"
>
×
</button>
</div> </div>
</div> </>
); );
} }
export default function ScheduleEditor({
initialData = INITIAL_DATA,
columns = COLUMNS,
onChange,
}) {
const [items, setItems] = useState(initialData);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
}),
useSensor(TouchSensor, {
activationConstraint: {
delay: 200,
tolerance: 8,
},
})
);
const updateItems = (newItems) => {
setItems(newItems);
onChange?.(newItems);
};
const handleDragEnd = (event) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
const newItems = arrayMove(items, oldIndex, newIndex);
// Update positions based on new order
const positions = newItems.map(item => item.position);
const reorderedItems = newItems.map((item, idx) => ({
...item,
position: positions[idx]
}));
updateItems(reorderedItems);
}
};
const handleUpdate = (id, field, value) => {
const newItems = items.map(item =>
item.id === id ? { ...item, [field]: value } : item
);
updateItems(newItems);
};
const handleDelete = (id) => {
const newItems = items.filter(item => item.id !== id);
updateItems(newItems);
};
const handleAddRow = () => {
const newId = String(Date.now());
const lastPosition = items.length > 0
? String(parseInt(items[items.length - 1].position || '0') + 1)
: '1';
const newItem = { id: newId, position: lastPosition };
columns.forEach(col => {
if (col.key !== 'position') {
newItem[col.key] = '';
}
});
updateItems([...items, newItem]);
};
const handleClear = () => {
updateItems([]);
};
return (
<div className="min-h-screen bg-gray-900 p-4 pb-24">
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold mb-6 text-white">
Schedule Editor
</h1>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={items.map(item => item.id)}
strategy={verticalListSortingStrategy}
>
<div>
{items.map((item) => (
<SortableRow
key={item.id}
item={item}
columns={columns}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
))}
</div>
</SortableContext>
</DndContext>
{items.length === 0 && (
<div className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500">
No items. Add a row to get started.
</div>
)}
<div className="mt-6 flex gap-3">
<button
onClick={handleAddRow}
className="flex-1 bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 active:bg-blue-800"
>
+ Add Row
</button>
<button
onClick={handleClear}
className="px-6 py-3 rounded-lg border-2 border-gray-700 text-gray-300 font-semibold hover:bg-gray-800 active:bg-gray-700"
>
Clear All
</button>
</div>
<div className="mt-6 p-4 rounded-lg bg-gray-800 text-sm text-gray-300">
<p className="font-bold mb-2 text-white">Instructions:</p>
<ul className="space-y-1">
<li> Drag rows by the handle () on the right to reorder</li>
<li> Positions move with their rows when dragged</li>
<li> Click any field to edit</li>
<li> Click × to delete a row</li>
</ul>
</div>
</div>
</div>
);
}
+212
View File
@@ -0,0 +1,212 @@
import React, { useState } from 'react';
import { DndContext, closestCenter, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
// Configuration
const COLUMNS = [
{ key: 'position', label: 'Pos', width: 'w-16', placeholder: '#' },
{ key: 'subject', label: 'Subject', width: 'flex-1', placeholder: 'Enter subject' },
{ key: 'time', label: 'Time', width: 'w-24', placeholder: '00:00' },
{ key: 'room', label: 'Room', width: 'w-20', placeholder: 'A-1' },
];
const INITIAL_DATA = [
{ id: '1', position: '1', subject: 'Mathematics', time: '09:00', room: 'A-1' },
{ id: '2', position: '2', subject: 'Physics', time: '10:00', room: 'B-2' },
{ id: '3', position: '3', subject: 'Chemistry', time: '11:00', room: 'A-3' },
{ id: '4', position: '4', subject: 'Biology', time: '13:00', room: 'C-1' },
];
function SortableRow({ item, columns, onUpdate, onDelete }) {
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: item.id });
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
};
return (
<div
ref={setNodeRef}
style={style}
className="bg-gray-800 rounded-lg overflow-hidden mb-2"
>
<div className="flex items-stretch">
{columns.map((col) => (
<input
key={col.key}
type="text"
value={item[col.key] || ''}
onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
className={`${col.width} px-3 py-3 bg-transparent text-white border-r border-gray-700 last:border-r-0 focus:outline-none focus:bg-gray-700/50 placeholder-gray-500`}
placeholder={col.placeholder}
/>
))}
<div
{...attributes}
{...listeners}
className="w-12 bg-gray-900 flex items-center justify-center cursor-move touch-none select-none text-gray-400 hover:text-white"
>
</div>
<button
onClick={() => onDelete(item.id)}
className="w-12 bg-red-600/20 text-red-400 hover:bg-red-600/40 hover:text-red-300 active:bg-red-600/60"
>
×
</button>
</div>
</div>
);
}
export default function ScheduleEditor({
initialData = INITIAL_DATA,
columns = COLUMNS,
onChange,
}) {
const [items, setItems] = useState(initialData);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
}),
useSensor(TouchSensor, {
activationConstraint: {
delay: 200,
tolerance: 8,
},
})
);
const updateItems = (newItems) => {
setItems(newItems);
onChange?.(newItems);
};
const handleDragEnd = (event) => {
const { active, over } = event;
if (over && active.id !== over.id) {
const oldIndex = items.findIndex((item) => item.id === active.id);
const newIndex = items.findIndex((item) => item.id === over.id);
const newItems = arrayMove(items, oldIndex, newIndex);
// Update positions based on new order
const positions = newItems.map(item => item.position);
const reorderedItems = newItems.map((item, idx) => ({
...item,
position: positions[idx]
}));
updateItems(reorderedItems);
}
};
const handleUpdate = (id, field, value) => {
const newItems = items.map(item =>
item.id === id ? { ...item, [field]: value } : item
);
updateItems(newItems);
};
const handleDelete = (id) => {
const newItems = items.filter(item => item.id !== id);
updateItems(newItems);
};
const handleAddRow = () => {
const newId = String(Date.now());
const lastPosition = items.length > 0
? String(parseInt(items[items.length - 1].position || '0') + 1)
: '1';
const newItem = { id: newId, position: lastPosition };
columns.forEach(col => {
if (col.key !== 'position') {
newItem[col.key] = '';
}
});
updateItems([...items, newItem]);
};
const handleClear = () => {
updateItems([]);
};
return (
<div className="min-h-screen bg-gray-900 p-4 pb-24">
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold mb-6 text-white">
Schedule Editor
</h1>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext
items={items.map(item => item.id)}
strategy={verticalListSortingStrategy}
>
<div>
{items.map((item) => (
<SortableRow
key={item.id}
item={item}
columns={columns}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
))}
</div>
</SortableContext>
</DndContext>
{items.length === 0 && (
<div className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500">
No items. Add a row to get started.
</div>
)}
<div className="mt-6 flex gap-3">
<button
onClick={handleAddRow}
className="flex-1 bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 active:bg-blue-800"
>
+ Add Row
</button>
<button
onClick={handleClear}
className="px-6 py-3 rounded-lg border-2 border-gray-700 text-gray-300 font-semibold hover:bg-gray-800 active:bg-gray-700"
>
Clear All
</button>
</div>
<div className="mt-6 p-4 rounded-lg bg-gray-800 text-sm text-gray-300">
<p className="font-bold mb-2 text-white">Instructions:</p>
<ul className="space-y-1">
<li> Drag rows by the handle () on the right to reorder</li>
<li> Positions move with their rows when dragged</li>
<li> Click any field to edit</li>
<li> Click × to delete a row</li>
</ul>
</div>
</div>
</div>
);
}
+32 -4
View File
@@ -53,7 +53,11 @@ export function usToRussianDay(usDay) {
export function formatDate(date) { export function formatDate(date) {
return `${ return `${
dayOfWeeksNames[usToRussianDay(date.getDay())] dayOfWeeksNames[usToRussianDay(date.getDay())]
}, ${date.getDate()} ${monthNames[date.getMonth()]}`; }, ${date.getDate()} ${monthNames[date.getMonth()]}${
date.getFullYear() != new Date().getFullYear()
? " " + date.getFullYear()
: ""
}`;
} }
export function apiFormatDate(date) { export function apiFormatDate(date) {
return ( return (
@@ -128,9 +132,33 @@ export function convertEditorToApi(arr) {
} }
export function isTimeBeforeNow(timeStr) { export function isTimeBeforeNow(timeStr) {
const [h, m] = timeStr.split(':').map(Number); const [h, m] = timeStr.split(":").map(Number);
if (!Number.isFinite(h) || !Number.isFinite(m)) return false; if (!Number.isFinite(h) || !Number.isFinite(m)) return false;
const now = new Date(); const now = new Date();
const t = new Date(now.getFullYear(), now.getMonth(), now.getDate(), h, m, 0, 0); const t = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
h,
m,
0,
0
);
return t.getTime() < now.getTime(); return t.getTime() < now.getTime();
} }
export const premsMeanings = {
"-1": "неограниченый",
// 0: "reserved"
1: "обычный",
2: "управляющий",
};
export const AvailableSymbols = (() => {
const a = "abcdefghijklmnopqrstuvwxyz"; // alphabet
const A = a.toUpperCase(); // ALPHABET
const n = "1234567890"; // numbers
const s = "_-"; // specials
return (a + n + A + s).split("");
})();
export const AvailableAdminSymbols = [...AvailableSymbols, ..."$()*^%@!+=.".split("")];
+18 -5
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
@@ -155,6 +155,7 @@ function SortableRow({
onDuplicate, onDuplicate,
setFocusEdit, setFocusEdit,
setHoverEdit, setHoverEdit,
pickmeMode,
}) { }) {
const { const {
attributes, attributes,
@@ -175,7 +176,9 @@ function SortableRow({
<div <div
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
className="bg-zinc-900 bg-grain rounded-lg mb-2" className={`${
pickmeMode ? "bg-pink-500/35" : "bg-zinc-900"
} bg-grain rounded-lg mb-2`}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex"> <div className="flex">
@@ -188,7 +191,11 @@ function SortableRow({
wrap="hard" wrap="hard"
rows={1} rows={1}
onChange={(e) => onUpdate(item.id, col.key, e.target.value)} onChange={(e) => onUpdate(item.id, col.key, e.target.value)}
className={`px-1 py-3 text-center bg-transparent text-white border-r border-zinc-700 last:border-r-0 last:rounded-tr-lg focus:outline-none focus:bg-gray-700/50 placeholder-gray-500 transition-colors hover:bg-gray-700/20 first:rounded-tl-lg max-h-min resize-none block min-w-0 grow ${col.width}`} className={`px-1 py-3 text-center bg-transparent text-white border-r ${
pickmeMode ? "border-pink-400/50" : "border-zinc-700"
} last:border-r-0 last:rounded-tr-lg focus:outline-none focus:bg-gray-700/50 placeholder-gray-500 transition-colors hover:bg-gray-700/20 first:rounded-tl-lg max-h-min resize-none block min-w-0 grow ${
col.width
}`}
placeholder={col.placeholder} placeholder={col.placeholder}
onFocus={() => setFocusEdit(col.key)} onFocus={() => setFocusEdit(col.key)}
onBlur={() => setFocusEdit(null)} onBlur={() => setFocusEdit(null)}
@@ -244,6 +251,7 @@ export function TableEditor({
adminId, adminId,
date, date,
dengiCash, dengiCash,
pickmeMode = false,
}) { }) {
const [items, setItems] = useState(initialData); const [items, setItems] = useState(initialData);
const [undoItems, setUndoItems] = useState([]); const [undoItems, setUndoItems] = useState([]);
@@ -553,7 +561,9 @@ export function TableEditor({
<span <span
key={col.key + i + "divider"} key={col.key + i + "divider"}
className="w-4 h-[1px] bg-zinc-600 -rotate-45 last:hidden" className={`w-4 h-[1px] ${
pickmeMode ? "bg-pink-600" : "bg-zinc-600"
} -rotate-45 last:hidden`}
/> />
</> </>
))} ))}
@@ -585,6 +595,7 @@ export function TableEditor({
onDuplicate={handleDuplicate} onDuplicate={handleDuplicate}
setFocusEdit={setFocusEdit} setFocusEdit={setFocusEdit}
setHoverEdit={setHoverEdit} setHoverEdit={setHoverEdit}
pickmeMode={pickmeMode}
/> />
</motion.div> </motion.div>
))} ))}
@@ -600,7 +611,9 @@ export function TableEditor({
opacity: 1, opacity: 1,
scale: 1, scale: 1,
}} }}
className="border-2 border-dashed border-gray-700 rounded-lg p-12 text-center text-gray-500" className={`border-2 border-dashed ${
pickmeMode ? "border-pink-700" : "border-gray-700"
} rounded-lg p-12 text-center text-gray-500`}
> >
пусто пусто
</motion.div> </motion.div>
+19 -73
View File
@@ -1,80 +1,26 @@
import { makeRequest } from "./req_mgr";
export async function EditorPrefillRequest(host, tab, adminid, date) { export async function EditorPrefillRequest(host, tab, adminid, date) {
try { return makeRequest(host + "?editor_data", {
const req = await fetch(host + "?editor_data", { adminId: adminid,
method: "POST", tab,
headers: { day: date,
"Content-Type": "application/x-www-form-urlencoded", });
},
body: new URLSearchParams({
adminId: adminid,
tab: tab,
day: date,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
export async function EditorPublishRequest(host, tab, adminid, date, newData) {
try {
const req = await fetch(host + "?editor_publish", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
adminId: adminid,
tab: tab,
day: date,
newData: newData,
}).toString(),
});
if ( export async function EditorPublishRequest(host, tab, adminid, date, newData) {
req.ok && return makeRequest(host + "?editor_publish", {
req.headers.get("Content-Type")?.includes("application/json") adminId: adminid,
) { tab,
return [1, await req.json()]; day: date,
} else { newData,
return [req.status, await req.text()]; });
}
} catch (err) {
return [-1, err];
}
} }
export async function EditorRemoveRequest(host, tab, adminid, date) { export async function EditorRemoveRequest(host, tab, adminid, date) {
try { return makeRequest(host + "?editor_fullitems_del", {
const req = await fetch(host + "?editor_fullitems_del", { adminId: adminid,
method: "POST", tab,
headers: { day: date,
"Content-Type": "application/x-www-form-urlencoded", });
},
body: new URLSearchParams({
adminId: adminid,
tab: tab,
day: date,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
+28 -21
View File
@@ -23,10 +23,7 @@ export function b64d(str) {
); );
} }
/** by claude /** by claude
* Universal copy-to-clipboard function
* Works with modern Clipboard API and falls back to legacy methods
* @param {string} text - The text to copy to clipboard * @param {string} text - The text to copy to clipboard
* @returns {Promise<boolean>} - Returns true if successful, false otherwise * @returns {Promise<boolean>} - Returns true if successful, false otherwise
*/ */
@@ -37,34 +34,44 @@ export async function copyToClipboard(text) {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
return true; return true;
} catch (err) { } catch (err) {
console.warn('Clipboard API failed, trying fallback:', err); console.warn("Clipboard API failed, trying fallback:", err);
} }
} }
// Method 2: Fallback for older browsers or non-secure contexts // Method 2: Fallback for older browsers or non-secure contexts
try { try {
const textarea = document.createElement('textarea'); const textarea = document.createElement("textarea");
textarea.value = text; textarea.value = text;
textarea.style.position = "fixed";
// Make it invisible and non-intrusive textarea.style.left = "-999999px";
textarea.style.position = 'fixed'; textarea.style.top = "-999999px";
textarea.style.left = '-999999px'; textarea.style.opacity = "0";
textarea.style.top = '-999999px'; textarea.setAttribute("readonly", "");
textarea.style.opacity = '0';
textarea.setAttribute('readonly', '');
document.body.appendChild(textarea); document.body.appendChild(textarea);
// Select and copy
textarea.select(); textarea.select();
textarea.setSelectionRange(0, text.length); textarea.setSelectionRange(0, text.length);
const success = document.execCommand("copy");
const success = document.execCommand('copy');
document.body.removeChild(textarea); document.body.removeChild(textarea);
return success; return success;
} catch (err) { } catch (err) {
console.error('All copy methods failed:', err); console.error("All copy methods failed:", err);
return false; return false;
} }
} }
// dx4 specific
export async function copyDXLink(classId, adminId = undefined) {
return copyToClipboard(
location.href.substring(0, location.href.search("#")) +
"#//" +
classId +
(adminId ? ";" + b64e(adminId) : "")
);
}
export const randomString = (length, symbols) => {
const len = symbols.length;
return Array.from(
{ length },
() => symbols[Math.floor(Math.random() * len)]
).join("");
};
-167
View File
@@ -1,167 +0,0 @@
/**
* MD5 hash implementation
* Based on: http://www.myersdaily.org/joseph/javascript/md5-text.html
*/
const HEX_CHARS = '0123456789abcdef';
// Pre-compute add32 function based on environment
const add32 = (() => {
const test = (a, b) => (a + b) & 0xFFFFFFFF;
// Test if simple addition works correctly
if (test(0x80000000, 0x80000000) === 0) {
return test;
}
// Fallback for environments with non-standard 32-bit arithmetic
return (x, y) => {
const lsw = (x & 0xFFFF) + (y & 0xFFFF);
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
};
})();
const cmn = (q, a, b, x, s, t) => {
a = add32(add32(a, q), add32(x, t));
return add32((a << s) | (a >>> (32 - s)), b);
};
const ff = (a, b, c, d, x, s, t) => cmn((b & c) | ((~b) & d), a, b, x, s, t);
const gg = (a, b, c, d, x, s, t) => cmn((b & d) | (c & (~d)), a, b, x, s, t);
const hh = (a, b, c, d, x, s, t) => cmn(b ^ c ^ d, a, b, x, s, t);
const ii = (a, b, c, d, x, s, t) => cmn(c ^ (b | (~d)), a, b, x, s, t);
const md5blk = (s) => {
const blks = [];
for (let i = 0; i < 64; i += 4) {
blks[i >> 2] = s.charCodeAt(i) +
(s.charCodeAt(i + 1) << 8) +
(s.charCodeAt(i + 2) << 16) +
(s.charCodeAt(i + 3) << 24);
}
return blks;
};
const md5cycle = (x, k) => {
let a = x[0], b = x[1], c = x[2], d = x[3];
a = ff(a, b, c, d, k[0], 7, -680876936);
d = ff(d, a, b, c, k[1], 12, -389564586);
c = ff(c, d, a, b, k[2], 17, 606105819);
b = ff(b, c, d, a, k[3], 22, -1044525330);
a = ff(a, b, c, d, k[4], 7, -176418897);
d = ff(d, a, b, c, k[5], 12, 1200080426);
c = ff(c, d, a, b, k[6], 17, -1473231341);
b = ff(b, c, d, a, k[7], 22, -45705983);
a = ff(a, b, c, d, k[8], 7, 1770035416);
d = ff(d, a, b, c, k[9], 12, -1958414417);
c = ff(c, d, a, b, k[10], 17, -42063);
b = ff(b, c, d, a, k[11], 22, -1990404162);
a = ff(a, b, c, d, k[12], 7, 1804603682);
d = ff(d, a, b, c, k[13], 12, -40341101);
c = ff(c, d, a, b, k[14], 17, -1502002290);
b = ff(b, c, d, a, k[15], 22, 1236535329);
a = gg(a, b, c, d, k[1], 5, -165796510);
d = gg(d, a, b, c, k[6], 9, -1069501632);
c = gg(c, d, a, b, k[11], 14, 643717713);
b = gg(b, c, d, a, k[0], 20, -373897302);
a = gg(a, b, c, d, k[5], 5, -701558691);
d = gg(d, a, b, c, k[10], 9, 38016083);
c = gg(c, d, a, b, k[15], 14, -660478335);
b = gg(b, c, d, a, k[4], 20, -405537848);
a = gg(a, b, c, d, k[9], 5, 568446438);
d = gg(d, a, b, c, k[14], 9, -1019803690);
c = gg(c, d, a, b, k[3], 14, -187363961);
b = gg(b, c, d, a, k[8], 20, 1163531501);
a = gg(a, b, c, d, k[13], 5, -1444681467);
d = gg(d, a, b, c, k[2], 9, -51403784);
c = gg(c, d, a, b, k[7], 14, 1735328473);
b = gg(b, c, d, a, k[12], 20, -1926607734);
a = hh(a, b, c, d, k[5], 4, -378558);
d = hh(d, a, b, c, k[8], 11, -2022574463);
c = hh(c, d, a, b, k[11], 16, 1839030562);
b = hh(b, c, d, a, k[14], 23, -35309556);
a = hh(a, b, c, d, k[1], 4, -1530992060);
d = hh(d, a, b, c, k[4], 11, 1272893353);
c = hh(c, d, a, b, k[7], 16, -155497632);
b = hh(b, c, d, a, k[10], 23, -1094730640);
a = hh(a, b, c, d, k[13], 4, 681279174);
d = hh(d, a, b, c, k[0], 11, -358537222);
c = hh(c, d, a, b, k[3], 16, -722521979);
b = hh(b, c, d, a, k[6], 23, 76029189);
a = hh(a, b, c, d, k[9], 4, -640364487);
d = hh(d, a, b, c, k[12], 11, -421815835);
c = hh(c, d, a, b, k[15], 16, 530742520);
b = hh(b, c, d, a, k[2], 23, -995338651);
a = ii(a, b, c, d, k[0], 6, -198630844);
d = ii(d, a, b, c, k[7], 10, 1126891415);
c = ii(c, d, a, b, k[14], 15, -1416354905);
b = ii(b, c, d, a, k[5], 21, -57434055);
a = ii(a, b, c, d, k[12], 6, 1700485571);
d = ii(d, a, b, c, k[3], 10, -1894986606);
c = ii(c, d, a, b, k[10], 15, -1051523);
b = ii(b, c, d, a, k[1], 21, -2054922799);
a = ii(a, b, c, d, k[8], 6, 1873313359);
d = ii(d, a, b, c, k[15], 10, -30611744);
c = ii(c, d, a, b, k[6], 15, -1560198380);
b = ii(b, c, d, a, k[13], 21, 1309151649);
a = ii(a, b, c, d, k[4], 6, -145523070);
d = ii(d, a, b, c, k[11], 10, -1120210379);
c = ii(c, d, a, b, k[2], 15, 718787259);
b = ii(b, c, d, a, k[9], 21, -343485551);
x[0] = add32(a, x[0]);
x[1] = add32(b, x[1]);
x[2] = add32(c, x[2]);
x[3] = add32(d, x[3]);
};
const rhex = (n) => {
let s = '';
for (let j = 0; j < 4; j++) {
s += HEX_CHARS[(n >> (j * 8 + 4)) & 0x0F] + HEX_CHARS[(n >> (j * 8)) & 0x0F];
}
return s;
};
const hex = (x) => x.map(rhex).join('');
const md51 = (s) => {
const n = s.length;
const state = [1732584193, -271733879, -1732584194, 271733878];
let i;
for (i = 64; i <= n; i += 64) {
md5cycle(state, md5blk(s.substring(i - 64, i)));
}
s = s.substring(i - 64);
const tail = new Array(16).fill(0);
for (i = 0; i < s.length; i++) {
tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
}
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
if (i > 55) {
md5cycle(state, tail);
tail.fill(0);
}
tail[14] = n * 8;
md5cycle(state, tail);
return state;
};
/**
* Calculate MD5 hash of a string
* @param {string} s - Input string
* @returns {string} MD5 hash in hexadecimal format
*/
export function md5(s) {
return hex(md51(s));
}
+41 -115
View File
@@ -1,127 +1,53 @@
import { apiFormatDate } from "./dengi"; import { apiFormatDate } from "./dengi";
export async function makeRequest(url, params) {
try {
const req = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(params).toString(),
});
return req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
? [1, await req.json()]
: [req.status, await req.text()];
} catch (err) {
return [-1, err];
}
}
export async function LoginReq(host, username) { export async function LoginReq(host, username) {
try { return makeRequest(host + "?login", { classId: username });
const req = await fetch(host + "?login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded", // 💀 важный заголовок
},
body: new URLSearchParams({
classId: username,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
export async function RaspGet(host, classId, day) { export async function RaspGet(host, classId, day) {
try { return makeRequest(host + "?req_client_schedule", {
const req = await fetch(host + "?req_client_schedule", { classId,
method: "POST", day: apiFormatDate(day),
headers: { });
"Content-Type": "application/x-www-form-urlencoded", // 💀 важный заголовок
},
body: new URLSearchParams({
classId: classId,
day: apiFormatDate(day),
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
}
export async function AdminReq(host, adminid) {
try {
const req = await fetch(host + "?admin_login", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded", // 💀 dengi
},
body: new URLSearchParams({
adminId: adminid,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
export async function AdminUserFRequest(host, adminid) { export async function AdminReq(host, adminId) {
try { return makeRequest(host + "?admin_login", { adminId });
const req = await fetch(host + "?get_admin_user_f", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
adminId: adminid,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
export async function classUserFRequest(host, classid) { export async function AdminUserFRequest(host, adminId) {
try { return makeRequest(host + "?get_admin_user_f", { adminId });
const req = await fetch(host + "?get_class_user_f", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
classId: classid,
}).toString(),
});
if (
req.ok &&
req.headers.get("Content-Type")?.includes("application/json")
) {
return [1, await req.json()];
} else {
return [req.status, await req.text()];
}
} catch (err) {
return [-1, err];
}
} }
export async function classUserFRequest(host, classId) {
return makeRequest(host + "?get_class_user_f", { classId });
}
/**@param {{action:string,person?:string,value?}} data */
export async function AdminUpdateRequest(host, adminId, data) {
return makeRequest(host + "?update_admin", { adminId, ...data });
}
/**@param {{action:string,value?}} data */
export async function ClassUpdateRequest(host, adminId, data) {
return makeRequest(host + "?classprofile_edit", { adminId, ...data });
}
export async function getClassProfile(host, adminId) {
return makeRequest(host + "?classprofile_view", { adminId });
}
+179 -27
View File
@@ -1,8 +1,15 @@
import React, { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { BiCheck, BiLeftArrowAlt, BiRightArrowAlt, BiX } from "react-icons/bi"; import {
BiCheck,
BiHide,
BiLeftArrowAlt,
BiRightArrowAlt,
BiShow,
BiX,
} from "react-icons/bi";
import { formatDate, getDateTags } from "./dengi.js"; import { formatDate, getDateTags } from "./dengi.js";
import { AnimatePresence, motion, LayoutGroup } from "motion/react"; import { AnimatePresence, motion, LayoutGroup } from "motion/react";
import trashcss from "./trash.module.css";
export function Switch({ export function Switch({
checked, checked,
defaultChecked = false, defaultChecked = false,
@@ -67,7 +74,7 @@ export function Switch({
); );
} }
export function LoadingSpinner(props) { export function LoadingSpinner({ size }) {
return ( return (
<svg <svg
aria-hidden="true" aria-hidden="true"
@@ -75,8 +82,8 @@ export function LoadingSpinner(props) {
viewBox="0 0 100 101" viewBox="0 0 100 101"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width={props.size} width={size}
height={props.size} height={size}
> >
<path <path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
@@ -90,10 +97,14 @@ export function LoadingSpinner(props) {
); );
} }
function DatePickerButton({ next = false, date, setDate }) { function DatePickerButton({ next = false, date, setDate, pink = false }) {
return ( return (
<span <span
className="cursor-pointer lg:hover:*:fill-black lg:hover:bg-white transition *:transition-colors rounded-full" className={`cursor-pointer ${
!pink
? "lg:hover:*:fill-black lg:hover:bg-white"
: "lg:hover:*:fill-white lg:hover:bg-pink-500"
} transition *:transition-colors rounded-full`}
onClick={() => { onClick={() => {
const d = new Date(date); const d = new Date(date);
d.setDate(d.getDate() + (next ? 1 : -1)); d.setDate(d.getDate() + (next ? 1 : -1));
@@ -105,17 +116,29 @@ function DatePickerButton({ next = false, date, setDate }) {
); );
} }
export const DatePicker = React.memo((props) => { export const DatePicker = (props) => {
const dateTag = getDateTags(props.date); const dateTag = getDateTags(props.date);
return ( return (
<LayoutGroup> <LayoutGroup>
<div className="flex items-center justify-between px-2 py-1 w-full bg-grain border-zinc-800/40 border rounded-2xl relative"> <div
<DatePickerButton date={props.date} setDate={props.setDate} /> className={`flex items-center justify-between px-2 py-1 w-full bg-grain ${
!props.pink ? "border-zinc-800/40" : "border-pink-500/20"
} border rounded-2xl relative`}
>
<DatePickerButton
date={props.date}
setDate={props.setDate}
pink={props.pink}
/>
<AnimatePresence mode="popLayout"> <AnimatePresence mode="popLayout">
<motion.span <motion.span
key={formatDate(props.date)} key={formatDate(props.date)}
className="cursor-pointer flex items-center gap-1 justify-center w-[80%] text-zinc-300 lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl lg:hover:bg-white px-3 overflow-hidden lg:hover:*:text-black" className={`cursor-pointer flex items-center gap-1 justify-center w-[80%] lg:hover:text-black *:transition-colors transition-colors p-1 rounded-3xl text-zinc-300 ${
!props.pink
? "lg:hover:bg-white lg:hover:*:text-black"
: "lg:hover:bg-pink-500 lg:hover:*:text-white"
} px-3 overflow-hidden `}
variants={{ variants={{
a: { opacity: 0, filter: "blur(1px)" }, a: { opacity: 0, filter: "blur(1px)" },
b: { opacity: 1, filter: "blur(0px)" }, b: { opacity: 1, filter: "blur(0px)" },
@@ -131,7 +154,9 @@ export const DatePicker = React.memo((props) => {
className={ className={
"block min-w-2 min-h-2 rounded-full mx-1 transition-colors" + "block min-w-2 min-h-2 rounded-full mx-1 transition-colors" +
(props.indicator (props.indicator
? " bg-zinc-300 animate-pulse" ? props.pink
? " bg-pink-500 animate-pulse"
: " bg-zinc-300 animate-pulse"
: " bg-transparent") : " bg-transparent")
} }
layoutId="datepicker_indicator" layoutId="datepicker_indicator"
@@ -151,7 +176,11 @@ export const DatePicker = React.memo((props) => {
layoutId="datepicker_date_div" layoutId="datepicker_date_div"
layout="position" layout="position"
> >
<span className="h-[1px] -rotate-50 bg-zinc-800 w-[20px] " /> <span
className={`h-[1px] -rotate-50 ${
props.pink ? "bg-pink-400/50" : "bg-zinc-800"
} w-[20px] `}
/>
</motion.div> </motion.div>
<motion.span <motion.span
initial={{ x: 10, opacity: 0 }} initial={{ x: 10, opacity: 0 }}
@@ -165,11 +194,16 @@ export const DatePicker = React.memo((props) => {
)} )}
</motion.span> </motion.span>
</AnimatePresence> </AnimatePresence>
<DatePickerButton next date={props.date} setDate={props.setDate} /> <DatePickerButton
next
date={props.date}
setDate={props.setDate}
pink={props.pink}
/>
</div> </div>
</LayoutGroup> </LayoutGroup>
); );
}); };
export function DengiButton({ export function DengiButton({
onClick, onClick,
@@ -235,23 +269,24 @@ export function DengiButton({
// ); // );
// } // }
const defaultDialogContent = {
message: "?",
yesBtn: "da",
noBtn: "net",
bgDissmis: true,
yesBlocked: false,
yesIcon: <BiCheck />,
noIcon: <BiX />,
noBtnStyle: "",
yesBtnStyle: "",
};
export function DengiYesOrNoDialog({ export function DengiYesOrNoDialog({
content = { content = defaultDialogContent,
message: "?",
yesBtn: "da",
noBtn: "net",
bgDissmis: true,
yesIcon: <BiCheck />,
noIcon: <BiX />,
noBtnStyle: "",
yesBtnStyle: "",
},
show, show,
onAnswer, onAnswer,
}) { }) {
return ( return (
<> <>
{/* Background */}
<AnimatePresence> <AnimatePresence>
{show && ( {show && (
<motion.div <motion.div
@@ -280,6 +315,7 @@ export function DengiYesOrNoDialog({
<DengiButton <DengiButton
className={content.yesBtnStyle + " w-full"} className={content.yesBtnStyle + " w-full"}
onClick={() => onAnswer(true)} onClick={() => onAnswer(true)}
disabled={content.yesBlocked}
> >
{content.yesIcon} {content.yesIcon}
{content.yesBtn} {content.yesBtn}
@@ -299,3 +335,119 @@ export function DengiYesOrNoDialog({
</> </>
); );
} }
export function DengiEditable({
value,
password = false,
disWHidden = true,
disabled = false,
showLoading = true,
minLenght = 1,
// eslint-disable-next-line no-unused-vars
onSubmit = ({ value, setIdle }) => {},
// eslint-disable-next-line no-unused-vars
onInput = ({ value, setIdle }) => {},
pink = false,
}) {
const [val, setVal] = useState(value);
const [hid, setHid] = useState(true);
const [idle, setIdle] = useState(false);
useEffect(() => {
setVal(value);
}, [value]);
return (
<form
action="#"
className="flex rounded-xl overflow-hidden max-w-50 h-8 relative"
onSubmit={(e) => {
if (idle) return;
e.preventDefault();
onSubmit({ value: val, setIdle });
}}
>
{value !== false || !showLoading ? (
<input
type={hid && password ? "password" : "text"}
value={val}
minLength={minLenght}
disabled={(disWHidden && password && hid) || disabled || idle}
className={`${pink ? "bg-pink-700/20" : "bg-zinc-800"} ${
val === value && !password ? "rounded-r-xl" : ""
} pl-2 rounded-l-xl w-full py-1 transition`}
onInput={(e) => {
setVal(e.target.value);
onInput({ value: e.target.value, setIdle });
}}
/>
) : (
<span
className={`w-full ${
pink ? "bg-pink-700/20" : "bg-zinc-800"
} py-1 w-full flex items-center justify-center`}
>
<LoadingSpinner size={25} />
</span>
)}
{password && (
<button
className={`${
pink
? "bg-pink-400 hover:bg-pink-600"
: "bg-zinc-600 hover:bg-zinc-700 "
} cursor-pointer transition-colors ${
val === value ? "rounded-r-xl" : ""
} p-1 text-2xl `}
onContextMenu={(e) => {
e.preventDefault();
if ((disWHidden && password && hid) || disabled || idle) return;
setVal(value);
}}
onClick={() => {
setHid(!hid);
}}
>
{hid ? <BiShow /> : <BiHide />}
</button>
)}
<AnimatePresence mode="popLayout">
{val !== value && (
<motion.button
initial={{ opacity: 0, translateX: 10 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0 }}
disabled={idle}
className={`${
pink
? "bg-pink-400 disabled:bg-pink-900/50 hover:bg-pink-600"
: "bg-zinc-600 disabled:bg-zinc-900 hover:bg-zinc-700"
} p-1 text-2xl rounded-r-xl transition-colors not-disabled:cursor-pointer`}
onContextMenu={(e) => {
e.preventDefault();
setVal(value);
}}
>
{idle ? <LoadingSpinner size={24} /> : <BiCheck />}
</motion.button>
)}
</AnimatePresence>
</form>
);
}
export function TextPreloader({ width = "5rem" }) {
return (
<span
className={"h-4 block bg-zinc-900 overflow-hidden rounded-full w-(--w)"}
style={{ "--w": width }}
>
<span
className={
"block h-10 -translate-y-3 w-3 bg-zinc-300 relative blur-[7px] " +
trashcss.anim_preloader
}
></span>
</span>
);
}
+11
View File
@@ -0,0 +1,11 @@
.anim_preloader {
animation: anim-preloader 1s infinite linear;
}
@keyframes anim-preloader {
from {
transform: translateX(-50px) rotate(20deg);
}
to {
transform: translateX(calc((var(--w, 50px)) + (50px))) rotate(20deg);
}
}
+6 -2
View File
@@ -1,5 +1,5 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc"; import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { VitePWA } from "vite-plugin-pwa"; import { VitePWA } from "vite-plugin-pwa";
@@ -7,7 +7,11 @@ import { VitePWA } from "vite-plugin-pwa";
export default defineConfig({ export default defineConfig({
base: "/dx4/", base: "/dx4/",
plugins: [ plugins: [
react(), react({
babel: {
plugins: [["babel-plugin-react-compiler"]],
},
}),
tailwindcss(), tailwindcss(),
VitePWA({ VitePWA({
registerType: "autoUpdate", registerType: "autoUpdate",