forked from Kieran/void.cat
Add profiles base
This commit is contained in:
parent
e6927fe6a8
commit
727a3b97a5
@ -36,7 +36,7 @@ public class AuthController : Controller
|
|||||||
var user = await _manager.Login(req.Username, req.Password);
|
var user = await _manager.Login(req.Username, req.Password);
|
||||||
var token = CreateToken(user);
|
var token = CreateToken(user);
|
||||||
var tokenWriter = new JwtSecurityTokenHandler();
|
var tokenWriter = new JwtSecurityTokenHandler();
|
||||||
return new(tokenWriter.WriteToken(token), null);
|
return new(tokenWriter.WriteToken(token), Profile: user.ToPublic());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -59,7 +59,7 @@ public class AuthController : Controller
|
|||||||
var newUser = await _manager.Register(req.Username, req.Password);
|
var newUser = await _manager.Register(req.Username, req.Password);
|
||||||
var token = CreateToken(newUser);
|
var token = CreateToken(newUser);
|
||||||
var tokenWriter = new JwtSecurityTokenHandler();
|
var tokenWriter = new JwtSecurityTokenHandler();
|
||||||
return new(tokenWriter.WriteToken(token), null);
|
return new(tokenWriter.WriteToken(token), Profile: newUser.ToPublic());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -74,9 +74,8 @@ public class AuthController : Controller
|
|||||||
|
|
||||||
var claims = new List<Claim>()
|
var claims = new List<Claim>()
|
||||||
{
|
{
|
||||||
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||||
new(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(6).ToUnixTimeSeconds().ToString()),
|
new(ClaimTypes.Expiration, DateTimeOffset.UtcNow.AddHours(6).ToUnixTimeSeconds().ToString())
|
||||||
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
|
||||||
};
|
};
|
||||||
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a)));
|
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a)));
|
||||||
|
|
||||||
@ -102,5 +101,5 @@ public class AuthController : Controller
|
|||||||
public string Password { get; init; }
|
public string Password { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public record LoginResponse(string? Jwt, string? Error = null);
|
public record LoginResponse(string? Jwt, string? Error = null, VoidUser? Profile = null);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace VoidCat.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
[ResponseCache(Location = ResponseCacheLocation.Client, Duration = 60)]
|
||||||
public async Task<GlobalStats> GetGlobalStats()
|
public async Task<GlobalStats> GetGlobalStats()
|
||||||
{
|
{
|
||||||
var bw = await _statsReporter.GetBandwidth();
|
var bw = await _statsReporter.GetBandwidth();
|
||||||
|
@ -32,12 +32,14 @@ namespace VoidCat.Controllers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var uid = HttpContext.GetUserId();
|
||||||
var meta = new VoidFileMeta()
|
var meta = new VoidFileMeta()
|
||||||
{
|
{
|
||||||
MimeType = Request.Headers.GetHeader("V-Content-Type"),
|
MimeType = Request.Headers.GetHeader("V-Content-Type"),
|
||||||
Name = Request.Headers.GetHeader("V-Filename"),
|
Name = Request.Headers.GetHeader("V-Filename"),
|
||||||
Description = Request.Headers.GetHeader("V-Description"),
|
Description = Request.Headers.GetHeader("V-Description"),
|
||||||
Digest = Request.Headers.GetHeader("V-Full-Digest")
|
Digest = Request.Headers.GetHeader("V-Full-Digest"),
|
||||||
|
Uploader = uid
|
||||||
};
|
};
|
||||||
|
|
||||||
var digest = Request.Headers.GetHeader("V-Digest");
|
var digest = Request.Headers.GetHeader("V-Digest");
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -5,6 +7,12 @@ namespace VoidCat.Model;
|
|||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
public static Guid? GetUserId(this HttpContext context)
|
||||||
|
{
|
||||||
|
var claimSub = context?.User?.Claims?.FirstOrDefault(a => a.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
return Guid.TryParse(claimSub, out var g) ? g : null;
|
||||||
|
}
|
||||||
|
|
||||||
public static Guid FromBase58Guid(this string base58)
|
public static Guid FromBase58Guid(this string base58)
|
||||||
{
|
{
|
||||||
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
||||||
|
@ -52,6 +52,12 @@ public record VoidFileMeta : IVoidFileMeta
|
|||||||
/// SHA-256 hash of the file
|
/// SHA-256 hash of the file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Digest { get; init; }
|
public string? Digest { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// User who uploaded the file
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
|
public Guid? Uploader { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using VoidCat.Model;
|
||||||
|
|
||||||
namespace VoidCat.Model;
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
@ -20,6 +21,19 @@ public abstract class VoidUser
|
|||||||
public DateTimeOffset Created { get; init; }
|
public DateTimeOffset Created { get; init; }
|
||||||
|
|
||||||
public DateTimeOffset LastLogin { get; set; }
|
public DateTimeOffset LastLogin { get; set; }
|
||||||
|
|
||||||
|
public string? Avatar { get; set; }
|
||||||
|
|
||||||
|
public PublicVoidUser ToPublic()
|
||||||
|
{
|
||||||
|
return new(Id, Email)
|
||||||
|
{
|
||||||
|
Roles = Roles,
|
||||||
|
Created = Created,
|
||||||
|
LastLogin = LastLogin,
|
||||||
|
Avatar = Avatar
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PrivateVoidUser : VoidUser
|
public sealed class PrivateVoidUser : VoidUser
|
||||||
|
@ -104,6 +104,7 @@ public class LocalDiskFileStore : IFileStore
|
|||||||
Description = payload.Meta.Description,
|
Description = payload.Meta.Description,
|
||||||
Digest = payload.Meta.Digest,
|
Digest = payload.Meta.Digest,
|
||||||
MimeType = payload.Meta.MimeType,
|
MimeType = payload.Meta.MimeType,
|
||||||
|
Uploader = payload.Meta.Uploader,
|
||||||
Uploaded = DateTimeOffset.UtcNow,
|
Uploaded = DateTimeOffset.UtcNow,
|
||||||
EditSecret = Guid.NewGuid(),
|
EditSecret = Guid.NewGuid(),
|
||||||
Size = total
|
Size = total
|
||||||
|
@ -3,12 +3,13 @@ import {Link} from "react-router-dom";
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {FormatBytes} from "../Util";
|
import {FormatBytes} from "../Util";
|
||||||
import {AdminApi} from "../Api";
|
import {useApi} from "../Api";
|
||||||
import {logout} from "../LoginState";
|
import {logout} from "../LoginState";
|
||||||
import {PagedSortBy, PageSortOrder} from "../Const";
|
import {PagedSortBy, PageSortOrder} from "../Const";
|
||||||
import {PageSelector} from "../PageSelector";
|
import {PageSelector} from "../PageSelector";
|
||||||
|
|
||||||
export function FileList(props) {
|
export function FileList(props) {
|
||||||
|
const {AdminApi} = useApi();
|
||||||
const auth = useSelector((state) => state.login.jwt);
|
const auth = useSelector((state) => state.login.jwt);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [files, setFiles] = useState();
|
const [files, setFiles] = useState();
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {PagedSortBy, PageSortOrder} from "../Const";
|
import {PagedSortBy, PageSortOrder} from "../Const";
|
||||||
import {AdminApi} from "../Api";
|
import {useApi} from "../Api";
|
||||||
import {logout} from "../LoginState";
|
import {logout} from "../LoginState";
|
||||||
import {PageSelector} from "../PageSelector";
|
import {PageSelector} from "../PageSelector";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
||||||
export function UserList() {
|
export function UserList() {
|
||||||
|
const {AdminApi} = useApi();
|
||||||
const auth = useSelector((state) => state.login.jwt);
|
const auth = useSelector((state) => state.login.jwt);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [users, setUsers] = useState();
|
const [users, setUsers] = useState();
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
async function getJson(method, url, auth, body) {
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
|
export function useApi() {
|
||||||
|
const auth = useSelector(state => state.login.jwt);
|
||||||
|
|
||||||
|
async function getJson(method, url, body) {
|
||||||
let headers = {
|
let headers = {
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
};
|
};
|
||||||
@ -16,18 +21,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminApi = {
|
return {
|
||||||
fileList: (auth, pageReq) => getJson("POST", "/admin/file", auth, pageReq),
|
AdminApi: {
|
||||||
deleteFile: (auth, id) => getJson("DELETE", `/admin/file/${id}`, auth),
|
fileList: (pageReq) => getJson("POST", "/admin/file", pageReq),
|
||||||
userList: (auth, pageReq) => getJson("POST", `/admin/user`, auth, pageReq)
|
deleteFile: (id) => getJson("DELETE", `/admin/file/${id}`),
|
||||||
}
|
userList: (pageReq) => getJson("POST", `/admin/user`, pageReq)
|
||||||
|
},
|
||||||
export const Api = {
|
Api: {
|
||||||
stats: () => getJson("GET", "/stats"),
|
stats: () => getJson("GET", "/stats"),
|
||||||
fileInfo: (id) => getJson("GET", `/upload/${id}`),
|
fileInfo: (id) => getJson("GET", `/upload/${id}`),
|
||||||
setPaywallConfig: (id, cfg) => getJson("POST", `/upload/${id}/paywall`, undefined, cfg),
|
setPaywallConfig: (id, cfg) => getJson("POST", `/upload/${id}/paywall`, cfg),
|
||||||
createOrder: (id) => getJson("GET", `/upload/${id}/paywall`),
|
createOrder: (id) => getJson("GET", `/upload/${id}/paywall`),
|
||||||
getOrder: (file, order) => getJson("GET", `/upload/${file}/paywall/${order}`),
|
getOrder: (file, order) => getJson("GET", `/upload/${file}/paywall/${order}`),
|
||||||
login: (username, password) => getJson("POST", `/auth/login`, undefined, {username, password}),
|
login: (username, password) => getJson("POST", `/auth/login`, {username, password}),
|
||||||
register: (username, password) => getJson("POST", `/auth/register`, undefined, {username, password})
|
register: (username, password) => getJson("POST", `/auth/register`, {username, password})
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
@ -1,2 +1,12 @@
|
|||||||
.app {
|
.page {
|
||||||
|
width: 720px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.page {
|
||||||
|
width: 100vw;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
@ -6,6 +6,8 @@ import {HomePage} from "./HomePage";
|
|||||||
import {Admin} from "./Admin/Admin";
|
import {Admin} from "./Admin/Admin";
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import {UserLogin} from "./UserLogin";
|
||||||
|
import {Profile} from "./Profile";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -14,7 +16,9 @@ function App() {
|
|||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route exact path="/" element={<HomePage/>}/>
|
<Route exact path="/" element={<HomePage/>}/>
|
||||||
<Route path="/admin" element={<Admin/>}/>
|
<Route exact path="/login" element={<UserLogin/>}/>
|
||||||
|
<Route exact path="/u/:id" element={<Profile/>}/>
|
||||||
|
<Route exact path="/admin" element={<Admin/>}/>
|
||||||
<Route exact path="/:id" element={<FilePreview/>}/>
|
<Route exact path="/:id" element={<FilePreview/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
@ -2,10 +2,11 @@ import {useState} from "react";
|
|||||||
|
|
||||||
import {StrikePaywallConfig} from "./StrikePaywallConfig";
|
import {StrikePaywallConfig} from "./StrikePaywallConfig";
|
||||||
import {NoPaywallConfig} from "./NoPaywallConfig";
|
import {NoPaywallConfig} from "./NoPaywallConfig";
|
||||||
import {Api} from "./Api";
|
import {useApi} from "./Api";
|
||||||
import "./FileEdit.css";
|
import "./FileEdit.css";
|
||||||
|
|
||||||
export function FileEdit(props) {
|
export function FileEdit(props) {
|
||||||
|
const {Api} = useApi();
|
||||||
const file = props.file;
|
const file = props.file;
|
||||||
const [paywall, setPaywall] = useState(file.paywall?.service);
|
const [paywall, setPaywall] = useState(file.paywall?.service);
|
||||||
|
|
||||||
|
@ -2,9 +2,10 @@ import {FormatCurrency} from "./Util";
|
|||||||
import {PaywallServices} from "./Const";
|
import {PaywallServices} from "./Const";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {LightningPaywall} from "./LightningPaywall";
|
import {LightningPaywall} from "./LightningPaywall";
|
||||||
import {Api} from "./Api";
|
import {useApi} from "./Api";
|
||||||
|
|
||||||
export function FilePaywall(props) {
|
export function FilePaywall(props) {
|
||||||
|
const {Api} = useApi();
|
||||||
const file = props.file;
|
const file = props.file;
|
||||||
const pw = file.paywall;
|
const pw = file.paywall;
|
||||||
const paywallKey = `paywall-${file.id}`;
|
const paywallKey = `paywall-${file.id}`;
|
||||||
|
@ -5,11 +5,12 @@ import FeatherIcon from "feather-icons-react";
|
|||||||
import "./FilePreview.css";
|
import "./FilePreview.css";
|
||||||
import {FileEdit} from "./FileEdit";
|
import {FileEdit} from "./FileEdit";
|
||||||
import {FilePaywall} from "./FilePaywall";
|
import {FilePaywall} from "./FilePaywall";
|
||||||
import {Api} from "./Api";
|
import {useApi} from "./Api";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import {FormatBytes} from "./Util";
|
import {FormatBytes} from "./Util";
|
||||||
|
|
||||||
export function FilePreview() {
|
export function FilePreview() {
|
||||||
|
const {Api} = useApi();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const [info, setInfo] = useState();
|
const [info, setInfo] = useState();
|
||||||
const [order, setOrder] = useState();
|
const [order, setOrder] = useState();
|
||||||
|
@ -3,6 +3,7 @@ import {buf2hex, ConstName, FormatBytes} from "./Util";
|
|||||||
import {RateCalculator} from "./RateCalculator";
|
import {RateCalculator} from "./RateCalculator";
|
||||||
|
|
||||||
import "./FileUpload.css";
|
import "./FileUpload.css";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
const UploadState = {
|
const UploadState = {
|
||||||
NotStarted: 0,
|
NotStarted: 0,
|
||||||
@ -15,6 +16,7 @@ const UploadState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function FileUpload(props) {
|
export function FileUpload(props) {
|
||||||
|
const auth = useSelector(state => state.login.jwt);
|
||||||
const [speed, setSpeed] = useState(0);
|
const [speed, setSpeed] = useState(0);
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const [result, setResult] = useState();
|
const [result, setResult] = useState();
|
||||||
@ -112,6 +114,9 @@ export function FileUpload(props) {
|
|||||||
req.setRequestHeader("V-Content-Type", props.file.type);
|
req.setRequestHeader("V-Content-Type", props.file.type);
|
||||||
req.setRequestHeader("V-Filename", props.file.name);
|
req.setRequestHeader("V-Filename", props.file.name);
|
||||||
req.setRequestHeader("V-Digest", buf2hex(digest));
|
req.setRequestHeader("V-Digest", buf2hex(digest));
|
||||||
|
if (auth) {
|
||||||
|
req.setRequestHeader("Authorization", `Bearer ${auth}`);
|
||||||
|
}
|
||||||
if (typeof (editSecret) === "string") {
|
if (typeof (editSecret) === "string") {
|
||||||
req.setRequestHeader("V-EditSecret", editSecret);
|
req.setRequestHeader("V-EditSecret", editSecret);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.footer {
|
.footer {
|
||||||
margin-top: 10px;
|
margin-top: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
import "./FooterLinks.css"
|
import "./FooterLinks.css"
|
||||||
import StrikeLogo from "./image/strike.png";
|
import StrikeLogo from "./image/strike.png";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
|
||||||
|
export function FooterLinks(){
|
||||||
|
const auth = useSelector(state => state.login.jwt);
|
||||||
|
const profile = useSelector(state => state.login.profile);
|
||||||
|
|
||||||
export function FooterLinks(props){
|
|
||||||
return (
|
return (
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
<a href="https://discord.gg/8BkxTGs" target="_blank">Discord</a>
|
<a href="https://discord.gg/8BkxTGs" target="_blank">Discord</a>
|
||||||
<a href="https://invite.strike.me/KS0FYF" target="_blank">Get Strike <img src={StrikeLogo} alt="Strike logo"/> </a>
|
<a href="https://invite.strike.me/KS0FYF" target="_blank">Get Strike <img src={StrikeLogo} alt="Strike logo"/> </a>
|
||||||
<a href="https://github.com/v0l/void.cat" target="_blank">GitHub</a>
|
<a href="https://github.com/v0l/void.cat" target="_blank">GitHub</a>
|
||||||
|
{!auth ?
|
||||||
|
<Link to={"/login"}>Login</Link> :
|
||||||
|
<Link to={`/u/${profile?.id}`}>Profile</Link>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -3,9 +3,10 @@ import FeatherIcon from "feather-icons-react";
|
|||||||
import {FormatBytes} from "./Util";
|
import {FormatBytes} from "./Util";
|
||||||
|
|
||||||
import "./GlobalStats.css";
|
import "./GlobalStats.css";
|
||||||
import {Api} from "./Api";
|
import {useApi} from "./Api";
|
||||||
|
|
||||||
export function GlobalStats(props) {
|
export function GlobalStats(props) {
|
||||||
|
const {Api} = useApi();
|
||||||
let [stats, setStats] = useState();
|
let [stats, setStats] = useState();
|
||||||
|
|
||||||
async function loadStats() {
|
async function loadStats() {
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
.home {
|
|
||||||
width: 720px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.home {
|
|
||||||
width: 100vw;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,11 +2,9 @@
|
|||||||
import {GlobalStats} from "./GlobalStats";
|
import {GlobalStats} from "./GlobalStats";
|
||||||
import {FooterLinks} from "./FooterLinks";
|
import {FooterLinks} from "./FooterLinks";
|
||||||
|
|
||||||
import "./HomePage.css";
|
|
||||||
|
|
||||||
export function HomePage() {
|
export function HomePage() {
|
||||||
return (
|
return (
|
||||||
<div className="home">
|
<div className="page">
|
||||||
<Dropzone/>
|
<Dropzone/>
|
||||||
<GlobalStats/>
|
<GlobalStats/>
|
||||||
<FooterLinks/>
|
<FooterLinks/>
|
||||||
|
@ -3,9 +3,10 @@ import {useEffect} from "react";
|
|||||||
|
|
||||||
import {Countdown} from "./Countdown";
|
import {Countdown} from "./Countdown";
|
||||||
import {PaywallOrderState} from "./Const";
|
import {PaywallOrderState} from "./Const";
|
||||||
import {Api} from "./Api";
|
import {useApi} from "./Api";
|
||||||
|
|
||||||
export function LightningPaywall(props) {
|
export function LightningPaywall(props) {
|
||||||
|
const {Api} = useApi();
|
||||||
const file = props.file;
|
const file = props.file;
|
||||||
const order = props.order;
|
const order = props.order;
|
||||||
const onPaid = props.onPaid;
|
const onPaid = props.onPaid;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useDispatch} from "react-redux";
|
import {useDispatch} from "react-redux";
|
||||||
import {setAuth} from "./LoginState";
|
import {setAuth} from "./LoginState";
|
||||||
|
import {useApi} from "./Api";
|
||||||
import "./Login.css";
|
import "./Login.css";
|
||||||
import {Api} from "./Api";
|
|
||||||
|
|
||||||
export function Login() {
|
export function Login() {
|
||||||
|
const {Api} = useApi();
|
||||||
const [username, setUsername] = useState();
|
const [username, setUsername] = useState();
|
||||||
const [password, setPassword] = useState();
|
const [password, setPassword] = useState();
|
||||||
const [error, setError] = useState();
|
const [error, setError] = useState();
|
||||||
@ -19,7 +19,7 @@ export function Login() {
|
|||||||
if (req.ok) {
|
if (req.ok) {
|
||||||
let rsp = await req.json();
|
let rsp = await req.json();
|
||||||
if (rsp.jwt) {
|
if (rsp.jwt) {
|
||||||
dispatch(setAuth(rsp.jwt));
|
dispatch(setAuth(rsp));
|
||||||
} else {
|
} else {
|
||||||
setError(rsp.error);
|
setError(rsp.error);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
import {createSlice} from "@reduxjs/toolkit";
|
import {createSlice} from "@reduxjs/toolkit";
|
||||||
|
|
||||||
const LocalStorageKey = "token";
|
const LocalStorageKey = "token";
|
||||||
|
const LocalStorageProfileKey = "profile";
|
||||||
|
|
||||||
export const LoginState = createSlice({
|
export const LoginState = createSlice({
|
||||||
name: "Login",
|
name: "Login",
|
||||||
initialState: {
|
initialState: {
|
||||||
jwt: window.localStorage.getItem(LocalStorageKey)
|
jwt: window.localStorage.getItem(LocalStorageKey),
|
||||||
|
profile: JSON.parse(window.localStorage.getItem(LocalStorageProfileKey))
|
||||||
},
|
},
|
||||||
reducers: {
|
reducers: {
|
||||||
setAuth: (state, action) => {
|
setAuth: (state, action) => {
|
||||||
state.jwt = action.payload;
|
state.jwt = action.payload.jwt;
|
||||||
|
state.profile = action.payload.profile;
|
||||||
window.localStorage.setItem(LocalStorageKey, state.jwt);
|
window.localStorage.setItem(LocalStorageKey, state.jwt);
|
||||||
|
window.localStorage.setItem(LocalStorageProfileKey, JSON.stringify(state.profile));
|
||||||
},
|
},
|
||||||
logout: (state) => {
|
logout: (state) => {
|
||||||
state.jwt = null;
|
state.jwt = null;
|
||||||
window.localStorage.removeItem(LocalStorageKey);
|
window.localStorage.removeItem(LocalStorageKey);
|
||||||
|
window.localStorage.removeItem(LocalStorageProfileKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
5
VoidCat/spa/src/Profile.js
Normal file
5
VoidCat/spa/src/Profile.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function Profile() {
|
||||||
|
return (
|
||||||
|
<h1>Coming soon..</h1>
|
||||||
|
);
|
||||||
|
}
|
21
VoidCat/spa/src/UserLogin.js
Normal file
21
VoidCat/spa/src/UserLogin.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import {Login} from "./Login";
|
||||||
|
import {useSelector} from "react-redux";
|
||||||
|
import {useNavigate} from "react-router-dom";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
export function UserLogin() {
|
||||||
|
const auth = useSelector((state) => state.login.jwt);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(auth){
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
}, [auth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page">
|
||||||
|
<Login/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user