diff --git a/VoidCat/spa/src/Admin/Admin.js b/VoidCat/spa/src/Admin/Admin.js index b4cfa56..090f9df 100644 --- a/VoidCat/spa/src/Admin/Admin.js +++ b/VoidCat/spa/src/Admin/Admin.js @@ -1,17 +1,17 @@ import {useDispatch, useSelector} from "react-redux"; -import {Login} from "../Login"; import {FileList} from "./FileList"; import {UserList} from "./UserList"; import "./Admin.css"; import {logout} from "../LoginState"; +import {Navigate} from "react-router-dom"; export function Admin() { const auth = useSelector((state) => state.login.jwt); const dispatch = useDispatch(); if (!auth) { - return ; + return ; } else { return (
diff --git a/VoidCat/spa/src/App.js b/VoidCat/spa/src/App.js index 54b752e..a0f33dd 100644 --- a/VoidCat/spa/src/App.js +++ b/VoidCat/spa/src/App.js @@ -4,16 +4,18 @@ import store from "./Store"; import {FilePreview} from "./FilePreview"; import {HomePage} from "./HomePage"; import {Admin} from "./Admin/Admin"; - -import './App.css'; import {UserLogin} from "./UserLogin"; import {Profile} from "./Profile"; +import {Header} from "./Header"; + +import './App.css'; function App() { return (
+
}/> }/> diff --git a/VoidCat/spa/src/Dropzone.css b/VoidCat/spa/src/Dropzone.css index a7ec39c..5e7916e 100644 --- a/VoidCat/spa/src/Dropzone.css +++ b/VoidCat/spa/src/Dropzone.css @@ -4,7 +4,7 @@ justify-content: center; border-radius: 20px; border: 2px dashed; - margin: 10vh 2px 2px; + margin: 5vh 2px 2px; text-align: center; user-select: none; cursor: pointer; diff --git a/VoidCat/spa/src/FilePreview.js b/VoidCat/spa/src/FilePreview.js index b370318..963eaaa 100644 --- a/VoidCat/spa/src/FilePreview.js +++ b/VoidCat/spa/src/FilePreview.js @@ -9,7 +9,7 @@ import {useApi} from "./Api"; import {Helmet} from "react-helmet"; import {FormatBytes} from "./Util"; import {ApiHost} from "./Const"; -import {FileUploader} from "./FileUploader"; +import {InlineProfile} from "./InlineProfile"; export function FilePreview() { const {Api} = useApi(); @@ -122,7 +122,7 @@ export function FilePreview() { {FormatBytes(info?.metadata?.size ?? 0, 2)}
- {info.uploader ? : null} + {info.uploader ? : null} ) : "Not Found"} diff --git a/VoidCat/spa/src/FileUploader.js b/VoidCat/spa/src/FileUploader.js deleted file mode 100644 index da72327..0000000 --- a/VoidCat/spa/src/FileUploader.js +++ /dev/null @@ -1,26 +0,0 @@ -import {DefaultAvatar} from "./Const"; -import {Link} from "react-router-dom"; -import "./FileUploader.css"; - -export function FileUploader(props) { - const uploader = props.uploader; - - let avatarUrl = uploader.avatar ?? DefaultAvatar; - if(!avatarUrl.startsWith("http")){ - avatarUrl = `/d/${avatarUrl}`; - } - let avatarStyles = { - backgroundImage: `url(${avatarUrl})` - }; - - return ( -
- -
-
-
{uploader.displayName}
-
- -
- ) -} \ No newline at end of file diff --git a/VoidCat/spa/src/FooterLinks.js b/VoidCat/spa/src/FooterLinks.js index c3c332b..37c6aab 100644 --- a/VoidCat/spa/src/FooterLinks.js +++ b/VoidCat/spa/src/FooterLinks.js @@ -1,21 +1,12 @@ import "./FooterLinks.css" 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(){ return (
Discord Get Strike Strike logo GitHub - {!auth ? - Login : - Profile - }
); } \ No newline at end of file diff --git a/VoidCat/spa/src/Header.css b/VoidCat/spa/src/Header.css new file mode 100644 index 0000000..1a0e36b --- /dev/null +++ b/VoidCat/spa/src/Header.css @@ -0,0 +1,12 @@ +.header { + user-select: none; + display: flex; + padding: 5px 0; + align-items: center; +} + +.header .title { + font-size: 30px; + line-height: 2; + flex-grow: 1; +} \ No newline at end of file diff --git a/VoidCat/spa/src/Header.js b/VoidCat/spa/src/Header.js new file mode 100644 index 0000000..fdbfe53 --- /dev/null +++ b/VoidCat/spa/src/Header.js @@ -0,0 +1,23 @@ +import "./Header.css"; +import {Link} from "react-router-dom"; +import {useSelector} from "react-redux"; +import {InlineProfile} from "./InlineProfile"; + +export function Header() { + const profile = useSelector(state => state.login.profile); + + return ( +
+
+ void.cat +
+ {profile ? + : + +
Login
+ } +
+ ) +} \ No newline at end of file diff --git a/VoidCat/spa/src/FileUploader.css b/VoidCat/spa/src/InlineProfile.css similarity index 57% rename from VoidCat/spa/src/FileUploader.css rename to VoidCat/spa/src/InlineProfile.css index 7145572..1d80d53 100644 --- a/VoidCat/spa/src/FileUploader.css +++ b/VoidCat/spa/src/InlineProfile.css @@ -1,14 +1,10 @@ -.uploader-info { - margin-top: 10px; - text-align: start; -} -.uploader-info .small-profile { +.small-profile { display: inline-flex; align-items: center; } -.uploader-info .small-profile .avatar { +.small-profile .avatar { width: 64px; height: 64px; border-radius: 16px; @@ -17,6 +13,6 @@ background-repeat: no-repeat; } -.uploader-info .small-profile .name { +.small-profile .name { padding-left: 15px; } \ No newline at end of file diff --git a/VoidCat/spa/src/InlineProfile.js b/VoidCat/spa/src/InlineProfile.js new file mode 100644 index 0000000..c6fe873 --- /dev/null +++ b/VoidCat/spa/src/InlineProfile.js @@ -0,0 +1,38 @@ +import "./InlineProfile.css"; +import {DefaultAvatar} from "./Const"; +import {Link} from "react-router-dom"; + +const DefaultSize = 64; + +export function InlineProfile(props) { + const profile = props.profile; + const options = { + size: DefaultSize, + showName: true, + link: true, + ...props.options + }; + + let avatarUrl = profile.avatar ?? DefaultAvatar; + if (!avatarUrl.startsWith("http")) { + avatarUrl = `/d/${avatarUrl}`; + } + let avatarStyles = { + backgroundImage: `url(${avatarUrl})` + }; + if (options.size !== DefaultSize) { + avatarStyles.width = `${options.size}px`; + avatarStyles.height = `${options.size}px`; + } + + let elms = ( +
+
+ {options.showName ?
{profile.displayName}
: null} +
+ ); + if (options.link === true) { + return {elms} + } + return elms; +} \ No newline at end of file diff --git a/VoidCat/spa/src/LoginState.js b/VoidCat/spa/src/LoginState.js index ffbaac1..94a08f7 100644 --- a/VoidCat/spa/src/LoginState.js +++ b/VoidCat/spa/src/LoginState.js @@ -22,6 +22,7 @@ export const LoginState = createSlice({ }, logout: (state) => { state.jwt = null; + state.profile = null; window.localStorage.removeItem(LocalStorageKey); window.localStorage.removeItem(LocalStorageProfileKey); } diff --git a/VoidCat/spa/src/Profile.css b/VoidCat/spa/src/Profile.css index 03f9d8c..b2674af 100644 --- a/VoidCat/spa/src/Profile.css +++ b/VoidCat/spa/src/Profile.css @@ -39,6 +39,10 @@ opacity: 1; } -.profile .roles { - margin: 20px 0; +.profile .roles > span { + margin-right: 10px; +} + +.profile dt { + font-weight: bold; } \ No newline at end of file diff --git a/VoidCat/spa/src/Profile.js b/VoidCat/spa/src/Profile.js index 554880f..f9bca46 100644 --- a/VoidCat/spa/src/Profile.js +++ b/VoidCat/spa/src/Profile.js @@ -4,12 +4,15 @@ import {useApi} from "./Api"; import {ApiHost, DefaultAvatar} from "./Const"; import "./Profile.css"; import {useDispatch, useSelector} from "react-redux"; -import {setProfile as setGlobalProfile} from "./LoginState"; +import {logout, setProfile as setGlobalProfile} from "./LoginState"; import {DigestAlgo} from "./FileUpload"; import {buf2hex} from "./Util"; +import moment from "moment"; +import FeatherIcon from "feather-icons-react"; export function Profile() { const [profile, setProfile] = useState(); + const [saved, setSaved] = useState(false); const auth = useSelector(state => state.login.jwt); const localProfile = useSelector(state => state.login.profile); const canEdit = localProfile?.id === profile?.id; @@ -52,7 +55,7 @@ export function Profile() { const file = res[0]; const buf = await file.arrayBuffer(); const digest = await crypto.subtle.digest(DigestAlgo, buf); - + let req = await fetch(`${ApiHost}/upload`, { mode: "cors", method: "POST", @@ -65,19 +68,19 @@ export function Profile() { "Authorization": `Bearer ${auth}` } }); - - if(req.ok) { + + if (req.ok) { let rsp = await req.json(); - if(rsp.ok) { + if (rsp.ok) { setProfile({ ...profile, avatar: rsp.file.id }); } } - - } - + + } + async function saveUser() { let r = await Api.updateUser({ id: profile.id, @@ -88,6 +91,7 @@ export function Profile() { if (r.ok) { // saved dispatch(setGlobalProfile(profile)); + setSaved(true); } } @@ -95,9 +99,15 @@ export function Profile() { loadProfile(); }, []); + useEffect(() => { + if (saved === true) { + setTimeout(() => setSaved(false), 1000); + } + }, [saved]); + if (profile) { let avatarUrl = profile.avatar ?? DefaultAvatar; - if(!avatarUrl.startsWith("http")) { + if (!avatarUrl.startsWith("http")) { // assume void-cat hosted avatar avatarUrl = `/d/${avatarUrl}`; } @@ -113,14 +123,24 @@ export function Profile() { onChange={(e) => editUsername(e.target.value)}/> : profile.displayName}
-
- {canEdit ?
changeAvatar()}> -

Edit

-
: null} -
-
-

Roles:

- {profile.roles.map(a => {a})} +
+
+
+ {canEdit ?
changeAvatar()}> +

Edit

+
: null} +
+
+
+
+
Created
+
{moment(profile.created).fromNow()}
+
Roles
+
{profile.roles.map(a => {a})}
+
Files
+
0
+
+
{canEdit ? @@ -129,7 +149,17 @@ export function Profile() { editPublic(e.target.checked)}/>

-
Save
+
+
+
Save
+
+
+ {saved ? : null} +
+
+
dispatch(logout())}>Logout
+
+
: null}
diff --git a/VoidCat/spa/src/index.css b/VoidCat/spa/src/index.css index 7423c6a..a3d36cd 100644 --- a/VoidCat/spa/src/index.css +++ b/VoidCat/spa/src/index.css @@ -29,4 +29,17 @@ a:hover { padding: 10px 20px; user-select: none; cursor: pointer; + margin: 5px; +} + +.flex { + display: flex; +} + +.flx-1 { + flex: 1; +} + +.flex-center { + align-items: center; } \ No newline at end of file