frontend improvements, yandex model iimplementations
Dev testing pipeline / BuildAndTest (push) Successful in 7m54s
Details
Dev testing pipeline / BuildAndTest (push) Successful in 7m54s
Details
This commit is contained in:
parent
e9b91ff4e3
commit
1621f90f0c
|
@ -26,6 +26,10 @@ namespace Yuna.Website.Server.API
|
||||||
.Produces(200)
|
.Produces(200)
|
||||||
.Produces(400);
|
.Produces(400);
|
||||||
|
|
||||||
|
app.MapPost("/api/auth/logout", Logout)
|
||||||
|
.WithTags("auth")
|
||||||
|
.Produces(200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,9 +41,18 @@ namespace Yuna.Website.Server.API
|
||||||
var hashedPassword = Encrypter.HashPassword(request.RawPassword, request.UserName);
|
var hashedPassword = Encrypter.HashPassword(request.RawPassword, request.UserName);
|
||||||
if (!hashedPassword.Equals(userFromDb.HashedPassword)) return Results.Unauthorized();
|
if (!hashedPassword.Equals(userFromDb.HashedPassword)) return Results.Unauthorized();
|
||||||
await SetAccessToken(context, tokenService, userFromDb);
|
await SetAccessToken(context, tokenService, userFromDb);
|
||||||
|
SetFrontendCookie(userFromDb, context);
|
||||||
return Results.Ok("");
|
return Results.Ok("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> Logout(HttpContext context, object? additionalInfo = null)
|
||||||
|
{
|
||||||
|
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
context.ClearCookies();
|
||||||
|
|
||||||
|
return Results.Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static async Task SetAccessToken(HttpContext context, ITokenService tokenService, User userFromDb)
|
private static async Task SetAccessToken(HttpContext context, ITokenService tokenService, User userFromDb)
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,13 +24,28 @@ namespace Yuna.Website.Server.Infrastructure
|
||||||
return default!;
|
return default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveToCookies<T>(this HttpContext context, string key, T value, TimeSpan? maxAge = null)
|
public static void SaveToCookies<T>(this HttpContext context, string key, T value, TimeSpan? maxAge = null, bool httpOnly = false)
|
||||||
{
|
{
|
||||||
var dataStr = System.Text.Json.JsonSerializer.Serialize<T>(value);
|
var dataStr = System.Text.Json.JsonSerializer.Serialize<T>(value);
|
||||||
|
|
||||||
if (context.Request.Cookies.ContainsKey(key)) context.Response.Cookies.Delete(key);
|
if (context.Request.Cookies.ContainsKey(key)) context.Response.Cookies.Delete(key);
|
||||||
|
|
||||||
context.Response.Cookies.Append(key, dataStr, new CookieOptions() { HttpOnly = true, MaxAge = maxAge ?? TimeSpan.FromDays(365) });
|
var sameSite = !Settings.IsDevEnv;
|
||||||
|
|
||||||
|
context.Response.Cookies.Append(key, dataStr, new CookieOptions()
|
||||||
|
{ HttpOnly = httpOnly,
|
||||||
|
MaxAge = maxAge ?? TimeSpan.FromDays(365),
|
||||||
|
SameSite = sameSite ? SameSiteMode.Strict : SameSiteMode.None ,
|
||||||
|
Secure = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearCookies(this HttpContext context)
|
||||||
|
{
|
||||||
|
foreach (var cookie in context.Request.Cookies)
|
||||||
|
{
|
||||||
|
context.Response.Cookies.Delete(cookie.Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long? GetUserIdFromCookie(this HttpContext context)
|
public static long? GetUserIdFromCookie(this HttpContext context)
|
||||||
|
|
|
@ -9,8 +9,11 @@ namespace Yuna.Website.Server.Infrastructure
|
||||||
{
|
{
|
||||||
Id = user.Id;
|
Id = user.Id;
|
||||||
Username = user.UserName;
|
Username = user.UserName;
|
||||||
|
IsAdmin = user.IsAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("isAdmin")]
|
||||||
|
public bool IsAdmin { get; }
|
||||||
|
|
||||||
[JsonPropertyName("username")]
|
[JsonPropertyName("username")]
|
||||||
public string Username { get; }
|
public string Username { get; }
|
||||||
|
|
|
@ -10,10 +10,14 @@ namespace Yuna.Website.Server.Infrastructure
|
||||||
public static string ReferalCode { get; private set; } = null!;
|
public static string ReferalCode { get; private set; } = null!;
|
||||||
public static string DbConnectionStr { get; private set; } = null!;
|
public static string DbConnectionStr { get; private set; } = null!;
|
||||||
|
|
||||||
|
public static bool IsDevEnv { get; private set; } = false;
|
||||||
|
public static bool IsProduction => !IsDevEnv;
|
||||||
public static string HttpsExternalUrl { get; private set; } = null!;
|
public static string HttpsExternalUrl { get; private set; } = null!;
|
||||||
|
|
||||||
public static void Init()
|
public static void Init(bool isDev = true)
|
||||||
{
|
{
|
||||||
|
IsDevEnv = isDev;
|
||||||
|
|
||||||
var jsonText = File.ReadAllText("globalSettings.json");
|
var jsonText = File.ReadAllText("globalSettings.json");
|
||||||
using JsonDocument document = JsonDocument.Parse(jsonText);
|
using JsonDocument document = JsonDocument.Parse(jsonText);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Yuna.Website.Server.Model.YandexDevice;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Model
|
||||||
|
{
|
||||||
|
public class DeviceMapping
|
||||||
|
{
|
||||||
|
public YandexDeviceType DeviceType { get; set; }
|
||||||
|
public Device Device { get; set; }
|
||||||
|
public Dictionary<long,YandexProp> PropMappings { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Model.YandexDevice
|
||||||
|
{
|
||||||
|
public enum YandexDeviceType
|
||||||
|
{
|
||||||
|
ClimateSensor = 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
namespace Yuna.Website.Server.Model.YandexDevice
|
||||||
|
{
|
||||||
|
public static class YandexExtensions
|
||||||
|
{
|
||||||
|
public static string ToJsonName(this YandexDeviceType type)
|
||||||
|
{
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case YandexDeviceType.ClimateSensor:
|
||||||
|
return "sensor.climate";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "yuna_error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToJsonName(this YandexPropType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case YandexPropType.Float:
|
||||||
|
return "float";
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "yuna_error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Model.YandexDevice
|
||||||
|
{
|
||||||
|
public class YandexParameter
|
||||||
|
{
|
||||||
|
[JsonPropertyName("instance")]
|
||||||
|
public string Instance { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("unit")]
|
||||||
|
public string Unit { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Yuna.Website.Server.Model.YandexDevice
|
||||||
|
{
|
||||||
|
public class YandexProp
|
||||||
|
{
|
||||||
|
public YandexProp()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public YandexProp(YandexPropType type, bool retrievable, bool reportable, YandexParameter parameter)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Retrievable = retrievable;
|
||||||
|
Reportable = reportable;
|
||||||
|
Parameter = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public YandexPropType Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string JsonPropName => Type.ToJsonName();
|
||||||
|
|
||||||
|
[JsonPropertyName("retrievable")]
|
||||||
|
public bool Retrievable { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonPropertyName("reportable")]
|
||||||
|
public bool Reportable { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonPropertyName("parameters")]
|
||||||
|
public YandexParameter Parameter { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Yuna.Website.Server.Model.YandexDevice
|
||||||
|
{
|
||||||
|
public enum YandexPropType
|
||||||
|
{
|
||||||
|
Float = 0,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Yuna.Website.Server.Services.MappingService
|
||||||
|
{
|
||||||
|
public interface IMappingService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ namespace Yuna.Website.Server
|
||||||
{
|
{
|
||||||
public static void LoadConfigs(WebApplicationBuilder builder)
|
public static void LoadConfigs(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
Settings.Init();
|
Settings.Init(builder.Environment.IsDevelopment());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -206,8 +206,8 @@ namespace Yuna.Website.Server
|
||||||
.AllowAnyMethod()
|
.AllowAnyMethod()
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowCredentials()
|
.AllowCredentials()
|
||||||
.SetIsOriginAllowed(origin => true) // Разрешить все источники
|
.SetIsOriginAllowed(origin => true) // Разрешить все источники
|
||||||
.WithExposedHeaders("Location")); // Разрешить заголовок Location)
|
.WithExposedHeaders("Location")); // Разрешить заголовок Location)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<DockerfileContext>..\..</DockerfileContext>
|
<DockerfileContext>..\..</DockerfileContext>
|
||||||
<SpaRoot>..\yuna.website.client</SpaRoot>
|
<SpaRoot>..\yuna.website.client</SpaRoot>
|
||||||
<SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
|
<SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand>
|
||||||
<SpaProxyServerUrl>https://localhost:5173</SpaProxyServerUrl>
|
<SpaProxyServerUrl>https://192.168.1.2:5173</SpaProxyServerUrl>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -10,12 +10,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.8.2",
|
"@chakra-ui/react": "^2.8.2",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"mobx": "^6.13.0",
|
"mobx": "^6.13.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.24.1"
|
"react-router-dom": "^6.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20.12.0",
|
"@types/node": "^20.12.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
@ -2582,6 +2584,12 @@
|
||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/js-cookie": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.6",
|
"version": "4.17.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz",
|
||||||
|
@ -4153,6 +4161,14 @@
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host 192.168.1.2 --port 5173",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
|
@ -12,12 +12,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "^2.8.2",
|
"@chakra-ui/react": "^2.8.2",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"mobx": "^6.13.0",
|
"mobx": "^6.13.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router-dom": "^6.24.1"
|
"react-router-dom": "^6.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20.12.0",
|
"@types/node": "^20.12.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
@ -31,4 +33,4 @@
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.3.1"
|
"vite": "^5.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,18 @@
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import "../resources/styles/header.scss"
|
import "../resources/styles/header.scss"
|
||||||
|
import { yunaGlobal } from "../utils/cookies/Yuna";
|
||||||
|
import { Button, Icon, IconButton } from "@chakra-ui/react";
|
||||||
|
import { MdOutlineLogout } from "react-icons/md";
|
||||||
|
import { LoginPageService } from "../pages/login/LoginPageService";
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onLogout = async () => {
|
||||||
|
await LoginPageService.logout();
|
||||||
|
navigate("/login");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -8,6 +20,29 @@ export const Header = () => {
|
||||||
paddingTop: 10
|
paddingTop: 10
|
||||||
}}>
|
}}>
|
||||||
<header className="header">
|
<header className="header">
|
||||||
<h1>Yuna</h1>
|
<h1 onClick={() => navigate("/")}>Yuna</h1>
|
||||||
</header></div>);
|
<nav>
|
||||||
|
<a onClick={() => navigate("/mappings")}>Маппинги</a>
|
||||||
|
</nav>
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
paddingRight: '5px'
|
||||||
|
}}>
|
||||||
|
<span >{'Приветик, ' + yunaGlobal.user?.username}
|
||||||
|
<IconButton
|
||||||
|
icon={<MdOutlineLogout />}
|
||||||
|
size='sm'
|
||||||
|
color="black"
|
||||||
|
colorScheme="white"
|
||||||
|
aria-label={"logout"}
|
||||||
|
onClick={() => onLogout()} /></span>
|
||||||
|
{yunaGlobal.user?.isAdmin && <span
|
||||||
|
style={{ paddingRight: 10 }}
|
||||||
|
className="micro-text important" >Ого, ты админ 💅</span>}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</div>);
|
||||||
}
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { Yuna } from "./utils/cookies/Yuna";
|
||||||
|
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var yuna: Yuna;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { }
|
|
@ -3,6 +3,9 @@ import ReactDOM from 'react-dom/client'
|
||||||
import { RouterProvider } from 'react-router-dom'
|
import { RouterProvider } from 'react-router-dom'
|
||||||
import "./resources/styles/common.scss"
|
import "./resources/styles/common.scss"
|
||||||
import { router } from './router'
|
import { router } from './router'
|
||||||
|
import { yunaGlobal } from './utils/cookies/Yuna'
|
||||||
|
|
||||||
|
globalThis.yuna = yunaGlobal;
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { EditModal } from "./components/modals/EditModal";
|
||||||
export const HomePage = () => {
|
export const HomePage = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Header />
|
|
||||||
<DeviceList />
|
<DeviceList />
|
||||||
<EditModal />
|
<EditModal />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -11,10 +11,10 @@ export const DeviceList = observer(() => {
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
< main className="main" >
|
< >
|
||||||
{homePageStore.isLoading ? <LoadingComponent /> :
|
{homePageStore.isLoading ? <LoadingComponent /> :
|
||||||
homePageStore.devices.length > 0
|
homePageStore.devices.length > 0
|
||||||
&& homePageStore.devices.map(x => <DeviceCard key={x.id} data={x} />)
|
&& homePageStore.devices.map(x => <DeviceCard key={x.id} data={x} />)
|
||||||
}
|
}
|
||||||
</main >);
|
</>);
|
||||||
});
|
});
|
|
@ -2,9 +2,29 @@ import { Button, Center, Input, Spinner } from "@chakra-ui/react";
|
||||||
import "../../resources/styles/loginPage.scss"
|
import "../../resources/styles/loginPage.scss"
|
||||||
import { observer } from "mobx-react"
|
import { observer } from "mobx-react"
|
||||||
import { LoginPageStore } from "./LoginPageStore";
|
import { LoginPageStore } from "./LoginPageStore";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
const store = new LoginPageStore();
|
const store = new LoginPageStore();
|
||||||
export const LoginPage = observer(() => {
|
export const LoginPage = observer(() => {
|
||||||
|
|
||||||
|
const loginRef = useRef<HTMLInputElement>(null);
|
||||||
|
const passwordRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleKeyDown = (key: string) => {
|
||||||
|
if (key === "ArrowDown") {
|
||||||
|
if (document.activeElement === loginRef.current) {
|
||||||
|
passwordRef.current?.focus();
|
||||||
|
}
|
||||||
|
} else if (key === "ArrowUp") {
|
||||||
|
if (document.activeElement === passwordRef.current) {
|
||||||
|
loginRef.current?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (key === "Enter") store.handleLogin();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container_wrapper">
|
<div className="container_wrapper">
|
||||||
<div className="login_container">
|
<div className="login_container">
|
||||||
|
@ -22,14 +42,18 @@ export const LoginPage = observer(() => {
|
||||||
</>
|
</>
|
||||||
: <>
|
: <>
|
||||||
<Input
|
<Input
|
||||||
|
ref={loginRef}
|
||||||
placeholder="Логин"
|
placeholder="Логин"
|
||||||
value={store.login}
|
value={store.login}
|
||||||
onChange={e => store.setLogin(e.target.value)}
|
onChange={e => store.setLogin(e.target.value)}
|
||||||
|
onKeyDown={x => handleKeyDown(x.key)}
|
||||||
marginBottom={5} />
|
marginBottom={5} />
|
||||||
<Input
|
<Input
|
||||||
|
ref={passwordRef}
|
||||||
placeholder="Пароль"
|
placeholder="Пароль"
|
||||||
type="password"
|
type="password"
|
||||||
marginBottom={7}
|
marginBottom={7}
|
||||||
|
onKeyDown={x => handleKeyDown(x.key)}
|
||||||
value={store.password}
|
value={store.password}
|
||||||
onChange={e => store.setPassword(e.target.value)} />
|
onChange={e => store.setPassword(e.target.value)} />
|
||||||
</>}
|
</>}
|
||||||
|
|
|
@ -22,6 +22,12 @@ export class LoginPageService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async logout(): Promise<boolean> {
|
||||||
|
const response = await api.post("auth/logout");
|
||||||
|
if (response.status === 200) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static async loginOauth(request: IOauthLoginRequest): Promise<IOauthLoginResult> {
|
public static async loginOauth(request: IOauthLoginRequest): Promise<IOauthLoginResult> {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { IOauthQueryParams } from "./types";
|
||||||
|
|
||||||
|
|
||||||
export class LoginPageStore {
|
export class LoginPageStore {
|
||||||
|
|
||||||
toast = createStandaloneToast();
|
toast = createStandaloneToast();
|
||||||
login: string = "";
|
login: string = "";
|
||||||
password: string = "";
|
password: string = "";
|
||||||
|
@ -84,7 +85,7 @@ export class LoginPageStore {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.replace("https://localhost:5173");
|
window.location.replace("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { observer } from "mobx-react"
|
||||||
|
|
||||||
|
export const MappingsPage = observer(() => {
|
||||||
|
return <>
|
||||||
|
</>
|
||||||
|
})
|
|
@ -1,5 +1,6 @@
|
||||||
* {
|
* {
|
||||||
font-family: 'Montserrat';
|
font-family: 'Montserrat';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container_wrapper {
|
.container_wrapper {
|
||||||
|
@ -15,4 +16,12 @@
|
||||||
font-display: block;
|
font-display: block;
|
||||||
font-family: 'Montserrat';
|
font-family: 'Montserrat';
|
||||||
src: url(../fonts/Montserrat-VariableFont_wght.ttf);
|
src: url(../fonts/Montserrat-VariableFont_wght.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
.micro-text {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.important {
|
||||||
|
color: red;
|
||||||
}
|
}
|
|
@ -2,6 +2,9 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background: #4FD1C5;
|
background: #4FD1C5;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
//border-bottom: solid black 1px;
|
//border-bottom: solid black 1px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
|
@ -10,10 +13,31 @@
|
||||||
/* x-offset y-offset blur-radius color */
|
/* x-offset y-offset blur-radius color */
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
padding-top: 10px;
|
transition: color 0.3s;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 30px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
import { ChakraProvider } from "@chakra-ui/react";
|
import { ChakraProvider } from "@chakra-ui/react";
|
||||||
import { Outlet, createBrowserRouter } from "react-router-dom";
|
import { Navigate, Outlet, createBrowserRouter } from "react-router-dom";
|
||||||
import { LoginPage } from "./pages/login/LoginPage";
|
import { LoginPage } from "./pages/login/LoginPage";
|
||||||
import { YunaTheme } from "./theme";
|
import { YunaTheme } from "./theme";
|
||||||
import { HomePage } from "./pages/home/HomePage";
|
import { HomePage } from "./pages/home/HomePage";
|
||||||
|
import { MappingsPage } from "./pages/mappings/MappingsPage";
|
||||||
|
import { Header } from "./components/Header";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,13 +15,31 @@ export const router = createBrowserRouter([
|
||||||
errorElement: <h1>Something gone wrong</h1>,
|
errorElement: <h1>Something gone wrong</h1>,
|
||||||
children:
|
children:
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
element: <Navigate to="/home" replace />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "home",
|
path: "home",
|
||||||
element: <HomePage />
|
element: <>
|
||||||
|
<Header />
|
||||||
|
<main className="main" >
|
||||||
|
<HomePage />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "mappings",
|
||||||
|
element: <>
|
||||||
|
<Header />
|
||||||
|
<main className="main" >
|
||||||
|
<MappingsPage />
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "settings",
|
path: "settings",
|
||||||
element: <>set</>
|
element: <><Header />set</>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "login",
|
path: "login",
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
class Yuna {
|
||||||
|
|
||||||
|
getUser() {
|
||||||
|
const cookieStr = Cookies.get("user");
|
||||||
|
if (cookieStr) {
|
||||||
|
const decodedCookie = decodeURIComponent(cookieStr);
|
||||||
|
return JSON.parse(decodedCookie) as IUserCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
user: IUserCookie | undefined = this.getUser()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const yunaGlobal = new Yuna();
|
||||||
|
|
||||||
|
|
||||||
|
interface IUserCookie {
|
||||||
|
isAdmin: boolean
|
||||||
|
username: string,
|
||||||
|
id: number
|
||||||
|
}
|
Loading…
Reference in New Issue