some changes
parent
d7addbb496
commit
92913efdc9
Binary file not shown.
|
After Width: | Height: | Size: 366 KiB |
|
|
@ -7,13 +7,14 @@
|
||||||
default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com;
|
default-src 'self' data: https://ssl.gstatic.com https://fonts.gstatic.com;
|
||||||
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
|
||||||
media-src *;
|
media-src *;
|
||||||
img-src 'self' data: content:;
|
img-src 'self' data: content: http://tinygraphs.com;
|
||||||
connect-src *;">
|
connect-src *;">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
</head>
|
</head>
|
||||||
<body style=" margin: 0px; overflow: hidden;">
|
<body style=" margin: 0px; overflow: hidden;">
|
||||||
<div id="app" style="width: 100vw; height: 100vh;"></div>
|
<div id="app" style="width: 100vw; height: 100vh;"></div>
|
||||||
|
<div id="portal-root"></div>
|
||||||
<script src="./src/index.tsx" type="module"></script>
|
<script src="./src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createContext, useCallback, useEffect, useState, useMemo } from 'react';
|
import { createContext, useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
import Channels from './pages/Channels';
|
import Channels from './pages/Channels';
|
||||||
import Chat from './pages/Chat';
|
import Chat from './pages/Chat';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/TwoPanel';
|
||||||
import NewAccount from './pages/NewAccount';
|
import NewAccount from './pages/NewAccount';
|
||||||
import ServerConnection from './components/ServerConnection';
|
import ServerConnection from './components/ServerConnection';
|
||||||
import EphemeralState from './contexts/EphemeralState/EphemeralState';
|
import EphemeralState from './contexts/EphemeralState/EphemeralState';
|
||||||
|
|
@ -23,7 +23,7 @@ export default function App() {
|
||||||
<>
|
<>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin='' />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin='' />
|
||||||
<link href={"https://fonts.googleapis.com/css2?family=Fira+Sans&family=Josefin+Sans&family=Lato&family=Radio+Canada&family=Readex+Pro&family=Red+Hat+Text&family=Rubik&family=Signika&family=Telex&display=swap"} rel="stylesheet" />
|
<link href={"https://fonts.googleapis.com/css2?family=Fira+Sans&family=Josefin+Sans&family=Lato&family=Radio+Canada&family=Readex+Pro&family=Red+Hat+Text:wght@200;300;400;500;600;700;800;900&family=Rubik&family=Signika&family=Telex&display=swap"} rel="stylesheet" />
|
||||||
<style>{`
|
<style>{`
|
||||||
html {
|
html {
|
||||||
--background: #282a36;
|
--background: #282a36;
|
||||||
|
|
@ -53,7 +53,7 @@ export default function App() {
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: transparent ? 'rgba(0, 0, 0, 0)' : 'var(--background)',
|
background: transparent ? 'rgba(0, 0, 0, 0)' : 'var(--neutral-3)',
|
||||||
color: transparent ? 'black' : 'var(--foreground)',
|
color: transparent ? 'black' : 'var(--foreground)',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
fontFamily: "'Red Hat Text', sans-serif",
|
fontFamily: "'Red Hat Text', sans-serif",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
|
import { useContext } from "react";
|
||||||
import ServerConnection from "./components/ServerConnection";
|
import ServerConnection from "./components/ServerConnection";
|
||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
|
import TwoPanel from "./components/TwoPanel";
|
||||||
|
import { SettingsContext } from "./contexts/EphemeralState/EphemeralState";
|
||||||
import useHomeServer from "./contexts/PersistentState/useHomeServerNative";
|
import useHomeServer from "./contexts/PersistentState/useHomeServerNative";
|
||||||
import useClientId from "./hooks/useClientId";
|
import useClientId from "./hooks/useClientId";
|
||||||
import useSessionToken from "./hooks/useSessionToken";
|
import useSessionToken from "./hooks/useSessionToken";
|
||||||
import Channels from "./pages/Channels";
|
import Channels from "./pages/Channels";
|
||||||
import Chat from "./pages/Chat";
|
import Chat from "./pages/Chat";
|
||||||
import NewAccount from "./pages/NewAccount";
|
import NewAccount from "./pages/NewAccount";
|
||||||
|
import Settings from "./pages/Settings";
|
||||||
|
|
||||||
interface RouterProps {
|
interface RouterProps {
|
||||||
[name: string]: React.ReactNode;
|
[name: string]: React.ReactNode;
|
||||||
|
|
@ -17,6 +21,7 @@ export default function Router(props: RouterProps) {
|
||||||
const { clientId } = useClientId();
|
const { clientId } = useClientId();
|
||||||
const { sessionToken } = useSessionToken();
|
const { sessionToken } = useSessionToken();
|
||||||
const { homeServer } = useHomeServer();
|
const { homeServer } = useHomeServer();
|
||||||
|
const { isSettingsOpen } = useContext(SettingsContext);
|
||||||
|
|
||||||
const configured =
|
const configured =
|
||||||
homeServer !== null &&
|
homeServer !== null &&
|
||||||
|
|
@ -26,13 +31,17 @@ export default function Router(props: RouterProps) {
|
||||||
return (
|
return (
|
||||||
configured ? (
|
configured ? (
|
||||||
<ServerConnection url={homeServer}>
|
<ServerConnection url={homeServer}>
|
||||||
<Sidebar
|
{isSettingsOpen ? (
|
||||||
threshold={800}
|
<Settings></Settings>
|
||||||
sidebar={300}
|
) : (
|
||||||
>
|
<TwoPanel
|
||||||
<Channels></Channels>
|
threshold={800}
|
||||||
<Chat></Chat>
|
sidebar={300}
|
||||||
</Sidebar>
|
>
|
||||||
|
<Sidebar></Sidebar>
|
||||||
|
<Chat></Chat>
|
||||||
|
</TwoPanel>
|
||||||
|
)}
|
||||||
</ServerConnection>
|
</ServerConnection>
|
||||||
) : (
|
) : (
|
||||||
<NewAccount></NewAccount>
|
<NewAccount></NewAccount>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
const Portal = ({children}: {children: React.ReactNode}) => {
|
||||||
|
const mount = document.getElementById("portal-root");
|
||||||
|
const el = document.createElement("div");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(mount === null) return;
|
||||||
|
mount.appendChild(el);
|
||||||
|
return () => {
|
||||||
|
mount.removeChild(el);
|
||||||
|
}
|
||||||
|
}, [el, mount]);
|
||||||
|
|
||||||
|
return createPortal(children, el)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Portal;
|
||||||
|
|
@ -1,89 +1,116 @@
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import useHomeServer from "../hooks/useHomeServer";
|
||||||
import useMediaQuery from '../lib/useMediaQueries';
|
import Channels from "../pages/Channels";
|
||||||
|
import pfp from '../../assets/pfp.jpg';
|
||||||
|
import { IoMdSettings } from 'react-icons/io';
|
||||||
|
import useHover from "../hooks/useHover";
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { SettingsContext } from "../contexts/EphemeralState/EphemeralState";
|
||||||
|
|
||||||
|
export default function Sidebar() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default function Sidebar(props: {
|
return (
|
||||||
threshold: number,
|
<div style={{
|
||||||
sidebar: number,
|
height: '100%',
|
||||||
children: any[]
|
display: 'grid',
|
||||||
}) {
|
gridTemplateRows: 'min-content 1fr min-content'
|
||||||
const bigScreen = useMediaQuery('(min-width:' + props.threshold + 'px)');
|
}}>
|
||||||
const [screenRef, setScreenRef] = useState<HTMLDivElement | null>(null);
|
<TopSidebar></TopSidebar>
|
||||||
const [startDrag, setStartDrag] = useState(0);
|
<Channels></Channels>
|
||||||
const [currentDrag, setCurrentDrag] = useState(0);
|
<MiniProfile></MiniProfile>
|
||||||
const [dragging, setDragging] = useState(false);
|
</div>
|
||||||
const [opened, setOpened] = useState(false);
|
)
|
||||||
|
}
|
||||||
const difference = opened ?
|
|
||||||
Math.min(currentDrag - startDrag, 0) :
|
function TopSidebar() {
|
||||||
Math.max(currentDrag - startDrag, 0);
|
|
||||||
|
const { homeServer } = useHomeServer();
|
||||||
const pointerDown = useCallback((e: any) => {
|
|
||||||
setDragging(true);
|
return (
|
||||||
setStartDrag(e.touches[0].clientX);
|
<div style={{
|
||||||
setCurrentDrag(e.touches[0].clientX);
|
lineHeight: '48px',
|
||||||
}, [dragging, startDrag, currentDrag]);
|
paddingLeft: '16px',
|
||||||
|
fontSize: '16px',
|
||||||
const pointerUp = useCallback(() => {
|
background: 'var(--neutral-3)',
|
||||||
setDragging(false);
|
boxShadow: 'black 0px 0px 3px 0px',
|
||||||
if(difference > 0) {
|
zIndex: '100',
|
||||||
setOpened(true);
|
fontWeight: '500',
|
||||||
} else if (difference < 0) {
|
}}>
|
||||||
setOpened(false);
|
{homeServer && new URL(homeServer).hostname.toLocaleLowerCase()}
|
||||||
}
|
</div>
|
||||||
}, [dragging, currentDrag, startDrag, opened]);
|
)
|
||||||
|
}
|
||||||
const pointerMove = useCallback((e: any) => {
|
|
||||||
setCurrentDrag(e.touches[0].clientX);
|
function MiniProfile() {
|
||||||
}, [dragging, currentDrag]);
|
return (
|
||||||
|
<div style={{
|
||||||
useEffect(() => {
|
fontSize: '16px',
|
||||||
if(screenRef === null) return;
|
background: 'var(--neutral-2)',
|
||||||
screenRef.addEventListener('touchstart', pointerDown, { passive: true });
|
// boxShadow: 'black 0px 0px 3px 0px',
|
||||||
screenRef.addEventListener('touchend', pointerUp, { passive: true });
|
zIndex: '100',
|
||||||
screenRef.addEventListener('touchmove', pointerMove, { passive: true });
|
fontWeight: '500',
|
||||||
// screenRef.addEventListener('pointercancel', pointerUp);
|
display: 'grid',
|
||||||
return () => {
|
gridTemplateColumns: 'min-content 1fr min-content'
|
||||||
screenRef.removeEventListener('touchstart', pointerDown);
|
}}>
|
||||||
screenRef.removeEventListener('touchend', pointerUp);
|
<ProfilePicture></ProfilePicture>
|
||||||
screenRef.removeEventListener('touchmove', pointerMove);
|
<div style={{
|
||||||
// screenRef.removeEventListener('pointercancel', pointerUp);
|
display: 'grid',
|
||||||
};
|
placeItems: 'center left',
|
||||||
}, [screenRef, pointerUp, pointerDown]);
|
}}>
|
||||||
|
<div>
|
||||||
return <div ref={setScreenRef} style={{
|
<div style={{
|
||||||
width: '100%',
|
fontWeight: '400',
|
||||||
height: '100%',
|
fontSize: '15px',
|
||||||
position: 'relative',
|
}}>Valerie</div>
|
||||||
userSelect: 'none',
|
<div style={{
|
||||||
// overflow: 'hidden',
|
fontWeight: '300',
|
||||||
}}>
|
fontSize: '13px',
|
||||||
<div
|
}}>dev.valnet.xyz</div>
|
||||||
style={{
|
</div>
|
||||||
// background: 'red',
|
</div>
|
||||||
width: bigScreen ? (props.sidebar + 'px') : '100%',
|
<div style={{
|
||||||
height: '100%',
|
whiteSpace: 'nowrap',
|
||||||
display: 'inline-block',
|
display: 'grid',
|
||||||
position: 'absolute',
|
gridAutoFlow: 'column',
|
||||||
top: '0px',
|
placeItems: 'center right',
|
||||||
left: bigScreen ? '0px' : !dragging ? (opened ? '0px' : '-100%') : `calc(${difference}px ${opened ? '' : '- 100%'})`,
|
paddingRight: '8px',
|
||||||
zIndex: '1',
|
}}>
|
||||||
transition: dragging ? 'none' : 'left 300ms linear, width 300ms linear',
|
<SettingsButton></SettingsButton>
|
||||||
}}
|
{/* <SettingsButton></SettingsButton>
|
||||||
>{props.children[0]}</div>
|
<SettingsButton></SettingsButton> */}
|
||||||
<div
|
</div>
|
||||||
style={{
|
</div>
|
||||||
// background: 'green',
|
)
|
||||||
width: bigScreen ? 'calc(100% - ' + props.sidebar + 'px)' : '100%',
|
}
|
||||||
height: '100%',
|
|
||||||
display: 'inline-block',
|
function SettingsButton() {
|
||||||
position: 'absolute',
|
const [ref, hover] = useHover<HTMLDivElement>();
|
||||||
top: '0px',
|
const { openSettings } = useContext(SettingsContext);
|
||||||
left: bigScreen ? (props.sidebar + 'px') : '0px',
|
|
||||||
zIndex: '0',
|
return <div ref={ref} className="settings" style={{
|
||||||
transition: 'left 300ms linear, width 300ms linear',
|
display: 'flex',
|
||||||
}}
|
padding: '8px',
|
||||||
>{props.children[1]}</div>
|
background: hover ?
|
||||||
</div>;
|
'var(--neutral-4)' :
|
||||||
|
'initial',
|
||||||
|
borderRadius: '5px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}} onClick={openSettings}>
|
||||||
|
<IoMdSettings size="16"></IoMdSettings>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProfilePicture() {
|
||||||
|
const name = 'Val';
|
||||||
|
return <div style={{
|
||||||
|
backgroundImage: `url(${pfp})`,
|
||||||
|
width: '40px',
|
||||||
|
height: '40px',
|
||||||
|
margin: '12px',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}></div>
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import useMediaQuery from '../lib/useMediaQueries';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function TwoPanel(props: {
|
||||||
|
threshold: number,
|
||||||
|
sidebar: number,
|
||||||
|
children: any
|
||||||
|
}) {
|
||||||
|
const bigScreen = useMediaQuery('(min-width:' + props.threshold + 'px)');
|
||||||
|
const [screenRef, setScreenRef] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [startDrag, setStartDrag] = useState(0);
|
||||||
|
const [currentDrag, setCurrentDrag] = useState(0);
|
||||||
|
const [dragging, setDragging] = useState(false);
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
const difference = opened ?
|
||||||
|
Math.min(currentDrag - startDrag, 0) :
|
||||||
|
Math.max(currentDrag - startDrag, 0);
|
||||||
|
|
||||||
|
const pointerDown = useCallback((e: any) => {
|
||||||
|
setDragging(true);
|
||||||
|
setStartDrag(e.touches[0].clientX);
|
||||||
|
setCurrentDrag(e.touches[0].clientX);
|
||||||
|
}, [dragging, startDrag, currentDrag]);
|
||||||
|
|
||||||
|
const pointerUp = useCallback(() => {
|
||||||
|
setDragging(false);
|
||||||
|
if(difference > 0) {
|
||||||
|
setOpened(true);
|
||||||
|
} else if (difference < 0) {
|
||||||
|
setOpened(false);
|
||||||
|
}
|
||||||
|
}, [dragging, currentDrag, startDrag, opened]);
|
||||||
|
|
||||||
|
const pointerMove = useCallback((e: any) => {
|
||||||
|
setCurrentDrag(e.touches[0].clientX);
|
||||||
|
}, [dragging, currentDrag]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(screenRef === null) return;
|
||||||
|
screenRef.addEventListener('touchstart', pointerDown, { passive: true });
|
||||||
|
screenRef.addEventListener('touchend', pointerUp, { passive: true });
|
||||||
|
screenRef.addEventListener('touchmove', pointerMove, { passive: true });
|
||||||
|
// screenRef.addEventListener('pointercancel', pointerUp);
|
||||||
|
return () => {
|
||||||
|
screenRef.removeEventListener('touchstart', pointerDown);
|
||||||
|
screenRef.removeEventListener('touchend', pointerUp);
|
||||||
|
screenRef.removeEventListener('touchmove', pointerMove);
|
||||||
|
// screenRef.removeEventListener('pointercancel', pointerUp);
|
||||||
|
};
|
||||||
|
}, [screenRef, pointerUp, pointerDown]);
|
||||||
|
|
||||||
|
return <div ref={setScreenRef} style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
userSelect: 'none',
|
||||||
|
// overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
// background: 'red',
|
||||||
|
width: bigScreen ? (props.sidebar + 'px') : '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'inline-block',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0px',
|
||||||
|
left: bigScreen ? '0px' : !dragging ? (opened ? '0px' : '-100%') : `calc(${difference}px ${opened ? '' : '- 100%'})`,
|
||||||
|
zIndex: '1',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: dragging ? 'none' : 'left 300ms linear, width 300ms linear',
|
||||||
|
}}
|
||||||
|
>{props.children[0]}</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
// background: 'green',
|
||||||
|
width: bigScreen ? 'calc(100% - ' + props.sidebar + 'px)' : '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'inline-block',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '0px',
|
||||||
|
left: bigScreen ? (props.sidebar + 'px') : '0px',
|
||||||
|
zIndex: '0',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: 'left 300ms linear, width 300ms linear',
|
||||||
|
}}
|
||||||
|
>{props.children[1]}</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,15 @@ export const ChannelContext = createContext<{
|
||||||
setChannel: () => {},
|
setChannel: () => {},
|
||||||
});
|
});
|
||||||
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
|
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
|
||||||
|
export const SettingsContext = createContext<{
|
||||||
|
openSettings: () => void,
|
||||||
|
closeSettings: () => void,
|
||||||
|
isSettingsOpen: boolean
|
||||||
|
}>({
|
||||||
|
openSettings() {},
|
||||||
|
closeSettings() {},
|
||||||
|
isSettingsOpen: false
|
||||||
|
});
|
||||||
|
|
||||||
export default function EphemeralState(props: {
|
export default function EphemeralState(props: {
|
||||||
onTransparencyChange: (value: boolean) => void,
|
onTransparencyChange: (value: boolean) => void,
|
||||||
|
|
@ -18,6 +26,8 @@ export default function EphemeralState(props: {
|
||||||
const [channel, setChannel] = useState<string | null>(null);
|
const [channel, setChannel] = useState<string | null>(null);
|
||||||
const [transparent, setTransparent] = useState(false);
|
const [transparent, setTransparent] = useState(false);
|
||||||
|
|
||||||
|
const [settings, setSettings] = useState(true);
|
||||||
|
|
||||||
const channelContextValue = useMemo(() => {
|
const channelContextValue = useMemo(() => {
|
||||||
return { channel, setChannel };
|
return { channel, setChannel };
|
||||||
}, [channel, setChannel]);
|
}, [channel, setChannel]);
|
||||||
|
|
@ -31,7 +41,13 @@ export default function EphemeralState(props: {
|
||||||
return (
|
return (
|
||||||
<ChannelContext.Provider value={channelContextValue}>
|
<ChannelContext.Provider value={channelContextValue}>
|
||||||
<TransparencyContext.Provider value={setTransparent}>
|
<TransparencyContext.Provider value={setTransparent}>
|
||||||
{props.children}
|
<SettingsContext.Provider value={{
|
||||||
|
openSettings: () => setSettings(true),
|
||||||
|
closeSettings: () => setSettings(false),
|
||||||
|
isSettingsOpen: settings,
|
||||||
|
}}>
|
||||||
|
{props.children}
|
||||||
|
</SettingsContext.Provider>
|
||||||
</TransparencyContext.Provider>
|
</TransparencyContext.Provider>
|
||||||
</ChannelContext.Provider>
|
</ChannelContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/TwoPanel';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
const container = document.getElementById('app');
|
const container = document.getElementById('app');
|
||||||
if(container !== null) {
|
if(container !== null) {
|
||||||
const root = ReactDOM.createRoot(container)
|
const root = ReactDOM.createRoot(container);
|
||||||
|
// const portal = createPortal()
|
||||||
root.render(<App></App>);
|
root.render(<App></App>);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Failed to initialize app, container not found!');
|
throw new Error('Failed to initialize app, container not found!');
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { ServerConnectionContext } from '../components/ServerConnection';
|
||||||
import useSessionToken from '../hooks/useSessionToken';
|
import useSessionToken from '../hooks/useSessionToken';
|
||||||
import { Router, router, RouterObject } from './api';
|
import { Router, router, RouterObject } from './api';
|
||||||
|
|
||||||
export function useApi(actions: Router | RouterObject, deps: any[]) {
|
export function useApi(actions: Router | RouterObject = {}, deps: any[] = []) {
|
||||||
const connection = useContext(ServerConnectionContext);
|
const connection = useContext(ServerConnectionContext);
|
||||||
const _router = typeof actions === 'object' ? router(actions) : actions;
|
const _router = typeof actions === 'object' ? router(actions) : actions;
|
||||||
const { sessionToken } = useSessionToken();
|
const { sessionToken } = useSessionToken();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import useHover from "../hooks/useHover";
|
||||||
|
|
||||||
|
export function BigButton(props: any) {
|
||||||
|
const [ref, hover] = useHover<HTMLDivElement>();
|
||||||
|
|
||||||
|
const angle = props.angle ?? 20;
|
||||||
|
const width = props.width ?? 'auto';
|
||||||
|
const display = !!props.inline ? 'inline-grid' : 'grid';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={ref} onClick={props.onClick ?? (() => { })} style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
width: width,
|
||||||
|
// margin: '4px',
|
||||||
|
display: display,
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
gridAutoFlow: 'column',
|
||||||
|
gridTemplateColumns: 'min-content 1fr',
|
||||||
|
background: (!!props.bright) ? (
|
||||||
|
props.selected ? 'var(--neutral-6)' :
|
||||||
|
hover ? 'var(--neutral-6)' :
|
||||||
|
'var(--neutral-5)'
|
||||||
|
) : (
|
||||||
|
props.selected ? 'var(--neutral-5)' :
|
||||||
|
hover ? 'var(--neutral-4)' :
|
||||||
|
'inherit'
|
||||||
|
),
|
||||||
|
transform: `skew(-${angle}deg, 0deg)`,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
padding: '4px',
|
||||||
|
display: 'flex',
|
||||||
|
transform: `skew(${angle}deg, 0deg)`,
|
||||||
|
}}>
|
||||||
|
{props.icon({
|
||||||
|
size: 16,
|
||||||
|
color: !!props.color ? props.color : (!!props.bright) ? (
|
||||||
|
props.selected ? 'var(--neutral-9)' :
|
||||||
|
hover ? 'var(--neutral-8)' :
|
||||||
|
'var(--neutral-8)'
|
||||||
|
) : (
|
||||||
|
props.selected ? 'var(--neutral-9)' :
|
||||||
|
hover ? 'var(--neutral-7)' :
|
||||||
|
'var(--neutral-7)'
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{
|
||||||
|
lineHeight: '24px',
|
||||||
|
paddingLeft: '4px',
|
||||||
|
paddingRight: '4px',
|
||||||
|
color: !!props.color ? props.color : (!!props.bright) ? (
|
||||||
|
props.selected ? 'var(--neutral-9)' :
|
||||||
|
hover ? 'var(--neutral-9)' :
|
||||||
|
'var(--neutral-9)'
|
||||||
|
) : (
|
||||||
|
props.selected ? 'var(--neutral-9)' :
|
||||||
|
hover ? 'var(--neutral-9)' :
|
||||||
|
'var(--neutral-7)'
|
||||||
|
),
|
||||||
|
transform: `skew(${angle}deg, 0deg)`,
|
||||||
|
}}>
|
||||||
|
{props.text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -22,12 +22,12 @@ export default function Channel(props: ChannelProps) {
|
||||||
gridTemplateColumns: 'min-content 1fr',
|
gridTemplateColumns: 'min-content 1fr',
|
||||||
color: selected ? 'cyan' : 'inherit',
|
color: selected ? 'cyan' : 'inherit',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: selected ? 'var(--neutral-4)' :
|
background: selected ? 'var(--neutral-5)' :
|
||||||
hover ? 'var(--neutral-3)' :
|
hover ? 'var(--neutral-4)' :
|
||||||
'inherit',
|
'inherit',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
// placeItems: 'left center',
|
transform:'skew(-20deg, 0deg)',
|
||||||
// border: '1px solid white'
|
transition: 'background 300ms, color 300ms',
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setChannel(uid);
|
setChannel(uid);
|
||||||
|
|
@ -35,17 +35,21 @@ export default function Channel(props: ChannelProps) {
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<CgHashtag color={
|
<CgHashtag color={
|
||||||
selected ? 'var(--neutral-9)' :
|
selected ? 'var(--neutral-9)' :
|
||||||
hover ? 'var(--neutral-7)' :
|
hover ? 'var(--neutral-7)' :
|
||||||
'var(--neutral-7)'
|
'var(--neutral-7)'
|
||||||
} size={24} style={{
|
} size={24} style={{
|
||||||
margin: '4px',
|
margin: '4px',
|
||||||
|
transition: 'background 300ms, color 300ms',
|
||||||
|
transform:'skew(-5deg, 0deg)',
|
||||||
}}></CgHashtag>
|
}}></CgHashtag>
|
||||||
<div style={{
|
<div style={{
|
||||||
lineHeight: '32px',
|
lineHeight: '32px',
|
||||||
color: selected ? 'var(--neutral-9)' :
|
color: selected ? 'var(--neutral-9)' :
|
||||||
hover ? 'var(--neutral-9)' :
|
hover ? 'var(--neutral-9)' :
|
||||||
'var(--neutral-7)'
|
'var(--neutral-7)',
|
||||||
|
transform:'skew(20deg, 0deg)',
|
||||||
|
transition: 'background 300ms, color 300ms',
|
||||||
}}>
|
}}>
|
||||||
{name.toLowerCase().replaceAll(' ', '-').trim()}
|
{name.toLowerCase().replaceAll(' ', '-').trim()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -44,10 +44,6 @@ export default function Channels() {
|
||||||
},
|
},
|
||||||
}, [channels, unreads]);
|
}, [channels, unreads]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// console.log('unreads', unreads);
|
|
||||||
}, [unreads]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(channels.length === 0) {
|
if(channels.length === 0) {
|
||||||
send('channels:list');
|
send('channels:list');
|
||||||
|
|
@ -55,10 +51,8 @@ export default function Channels() {
|
||||||
}, [channels]);
|
}, [channels]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log(channel, channels);
|
|
||||||
if(channels.length === 0) return;
|
if(channels.length === 0) return;
|
||||||
if(channel !== null) return;
|
if(channel !== null) return;
|
||||||
// console.log('this is what setChannel is', setChannel);
|
|
||||||
setChannel(channels[0].uid);
|
setChannel(channels[0].uid);
|
||||||
}, [channel, channels]);
|
}, [channel, channels]);
|
||||||
|
|
||||||
|
|
@ -86,8 +80,10 @@ export default function Channels() {
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: '#21222c',
|
background: 'var(--neutral-3)',
|
||||||
padding: '0px 8px'
|
padding: '0px 8px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
<br></br>
|
<br></br>
|
||||||
{channels.map(c => (
|
{channels.map(c => (
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ export default () => {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
|
background: 'var(--neutral-4)',
|
||||||
gridTemplateColumns: `1fr ${CHATBOX_SIZE}px`,
|
gridTemplateColumns: `1fr ${CHATBOX_SIZE}px`,
|
||||||
gridTemplateRows: `1fr ${CHATBOX_SIZE}px`,
|
gridTemplateRows: `1fr ${CHATBOX_SIZE}px`,
|
||||||
gridTemplateAreas: '"content content" "message send"',
|
gridTemplateAreas: '"content content" "message send"',
|
||||||
|
|
@ -102,7 +103,7 @@ export default () => {
|
||||||
margin: PADDING + 'px',
|
margin: PADDING + 'px',
|
||||||
marginRight: '0px',
|
marginRight: '0px',
|
||||||
borderRadius: ((CHATBOX_SIZE - PADDING*2) / 2) + 'px',
|
borderRadius: ((CHATBOX_SIZE - PADDING*2) / 2) + 'px',
|
||||||
background: '#343746',
|
background: 'var(--neutral-5)',
|
||||||
gridArea: 'message',
|
gridArea: 'message',
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
placeItems: 'center center',
|
placeItems: 'center center',
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function Message({
|
||||||
}}>
|
}}>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
color: '#596793',
|
color: 'var(--neutral-6)',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
marginRight: '16px',
|
marginRight: '16px',
|
||||||
|
|
@ -43,7 +43,7 @@ export function Message({
|
||||||
<span style={{
|
<span style={{
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontWeight: 'bold',
|
fontWeight: '500',
|
||||||
float: 'left',
|
float: 'left',
|
||||||
paddingRight: firstLineIndent,
|
paddingRight: firstLineIndent,
|
||||||
// marginRight: '16px',
|
// marginRight: '16px',
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
import { useEffect, useState } from "react";
|
import { forwardRef, useEffect, useState } from "react";
|
||||||
import { useCallback, useContext, useRef } from "react"
|
import { useCallback, useContext, useRef } from "react"
|
||||||
|
import { BiLogIn } from "react-icons/bi";
|
||||||
|
import { FaUserPlus } from 'react-icons/fa';
|
||||||
import ServerConnection from "../components/ServerConnection";
|
import ServerConnection from "../components/ServerConnection";
|
||||||
import { useApi } from "../lib/useApi";
|
import useHomeServer from "../contexts/PersistentState/useHomeServerNative";
|
||||||
import QR from 'qrcode';
|
import { BigButton } from "./BigButton";
|
||||||
import useSessionToken from "../hooks/useSessionToken";
|
import { SignUp } from "./SignUp";
|
||||||
|
import { MdOutlineNavigateNext } from 'react-icons/md';
|
||||||
|
import useHover from "../hooks/useHover";
|
||||||
|
import { AiOutlineEdit } from "react-icons/ai";
|
||||||
|
|
||||||
export default function NewAccount() {
|
export default function NewAccount() {
|
||||||
|
|
||||||
|
|
@ -12,7 +17,6 @@ export default function NewAccount() {
|
||||||
|
|
||||||
|
|
||||||
// const inputRef = useRef<HTMLInputElement>(null);
|
// const inputRef = useRef<HTMLInputElement>(null);
|
||||||
// const { setHomeServer } = useContext(HomeServerContext);
|
|
||||||
// const { setClientId } = useContext(ClientIdContext);
|
// const { setClientId } = useContext(ClientIdContext);
|
||||||
|
|
||||||
// const setTransparent = useContext(TransparencyContext);
|
// const setTransparent = useContext(TransparencyContext);
|
||||||
|
|
@ -62,12 +66,26 @@ export default function NewAccount() {
|
||||||
// }
|
// }
|
||||||
// }, [data, scanning])
|
// }, [data, scanning])
|
||||||
|
|
||||||
|
// const [homeServer, setHomeServer] = useState<string | null>(null);
|
||||||
|
// const homeServerInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const { setHomeServer, homeServer } = useHomeServer();
|
||||||
|
const [homeServerInput, setHomeServerInput] = useState<string>(homeServer ?? '');
|
||||||
|
const [usernameInput, setUsernameInput] = useState('');
|
||||||
|
const [authCodeInput, setAuthCodeInput] = useState('');
|
||||||
const [returning, setReturning] = useState(true);
|
const [returning, setReturning] = useState(true);
|
||||||
const homeServerInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [homeServer, setHomeServer] = useState<string | null>(null);
|
|
||||||
const [connection, setConnection] = useState<WebSocket | null>(null);
|
const [connection, setConnection] = useState<WebSocket | null>(null);
|
||||||
const [connecting, setConnecting] = useState(false);
|
const [connecting, setConnecting] = useState(false);
|
||||||
const [connectionError, setConnectionError] = useState('');
|
const [connectionError, setConnectionError] = useState('');
|
||||||
|
const [edittingHomeServer, setEdittingHomeServer] = useState(false);
|
||||||
|
const [homeServerInputRef, homeServerHovered] = useHover<HTMLInputElement>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(homeServer === null) {
|
||||||
|
setEdittingHomeServer(true)
|
||||||
|
} else {
|
||||||
|
setEdittingHomeServer(false)
|
||||||
|
}
|
||||||
|
}, [homeServer]);
|
||||||
|
|
||||||
const connect = useCallback((url: string) => {
|
const connect = useCallback((url: string) => {
|
||||||
if(connecting) return;
|
if(connecting) return;
|
||||||
|
|
@ -91,133 +109,240 @@ export default function NewAccount() {
|
||||||
ws.addEventListener('error', (e) => {
|
ws.addEventListener('error', (e) => {
|
||||||
setConnectionError('Connection failed')
|
setConnectionError('Connection failed')
|
||||||
});
|
});
|
||||||
}, [connecting])
|
}, [connecting]);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div style={{
|
||||||
|
// display: 'grid',
|
||||||
|
// placeContent: 'center center',
|
||||||
|
// height: '100%',
|
||||||
|
// textAlign: 'center'
|
||||||
|
// }}>
|
||||||
|
// {returning ? (
|
||||||
|
// <div>
|
||||||
|
// <span>
|
||||||
|
// Login
|
||||||
|
// </span>
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// <a href="#" onClick={() => setReturning(false)}>Sign up</a>
|
||||||
|
// </div>
|
||||||
|
// ) : (
|
||||||
|
// <>
|
||||||
|
// <div>
|
||||||
|
// <a href="#" onClick={() => setReturning(true)}>
|
||||||
|
// Login
|
||||||
|
// </a>
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// <span>
|
||||||
|
// Sign up
|
||||||
|
// </span>
|
||||||
|
// </div>
|
||||||
|
// <br></br>
|
||||||
|
// <label>Home Server URL</label>
|
||||||
|
// <input style={{textAlign: 'center'}} ref={homeServerInputRef} defaultValue="wss://macos.valnet.xyz" disabled={connection !== null || connecting}></input>
|
||||||
|
// <button onClick={() => connect(homeServerInputRef.current?.value ?? '')} disabled={connection !== null || connecting}>Next</button>
|
||||||
|
// {connecting ? `Connecting...` : connectionError}
|
||||||
|
// <br></br>
|
||||||
|
// {connection !== null && (
|
||||||
|
// <ServerConnection url={homeServer ?? ''}>
|
||||||
|
// <SignUp>
|
||||||
|
// </SignUp>
|
||||||
|
// </ServerConnection>
|
||||||
|
// )}
|
||||||
|
// {/* Create New Account!! <br />
|
||||||
|
// Enter Home Server URL <br />
|
||||||
|
// <input defaultValue="wss://dev.valnet.xyz" ref={inputRef}></input> <br />
|
||||||
|
// <button onClick={go}> GO </button> <br />
|
||||||
|
// <br />
|
||||||
|
// or scan a QR! <br />
|
||||||
|
// <button onClick={scanQr}>SCAN</button><br></br>
|
||||||
|
// <pre>
|
||||||
|
// {data}
|
||||||
|
// {scanning ? 'SCANNING' : 'NOT SCANNING'}
|
||||||
|
// </pre> */}
|
||||||
|
// </>
|
||||||
|
// )}
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'grid',
|
width: '100%',
|
||||||
placeContent: 'center center',
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
textAlign: 'center'
|
display: 'grid',
|
||||||
|
placeItems: 'center center',
|
||||||
|
background: 'var(--neutral-3)',
|
||||||
}}>
|
}}>
|
||||||
{returning ? (
|
<div style={{
|
||||||
<div>
|
width: '450px',
|
||||||
<span>
|
background: 'var(--neutral-4)',
|
||||||
Login
|
boxShadow: '0px 4px 20px 0px var(--neutral-1)',
|
||||||
</span>
|
borderRadius: '8px',
|
||||||
|
transform: 'skew(-6deg, 0deg)',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
<a href="#" onClick={() => setReturning(false)}>Sign up</a>
|
transform: 'skew(6deg, 0deg)',
|
||||||
</div>
|
margin: '8px',
|
||||||
) : (
|
}}>
|
||||||
<>
|
<div style={{
|
||||||
<div>
|
display: 'inline-block',
|
||||||
<a href="#" onClick={() => setReturning(true)}>
|
width: '50%',
|
||||||
Login
|
paddingRight: '4px',
|
||||||
</a>
|
boxSizing: 'border-box',
|
||||||
|
}}>
|
||||||
|
<BigButton
|
||||||
|
icon={BiLogIn}
|
||||||
<span>
|
text="Login"
|
||||||
Sign up
|
selected={returning}
|
||||||
</span>
|
angle={6}
|
||||||
|
width="100%"
|
||||||
|
inline={true}
|
||||||
|
onClick={() => setReturning(true)}
|
||||||
|
></BigButton>
|
||||||
</div>
|
</div>
|
||||||
<br></br>
|
<div style={{
|
||||||
<label>Home Server URL</label>
|
display: 'inline-block',
|
||||||
<input style={{textAlign: 'center'}} ref={homeServerInputRef} defaultValue="wss://macos.valnet.xyz" disabled={connection !== null || connecting}></input>
|
width: '50%',
|
||||||
<button onClick={() => connect(homeServerInputRef.current?.value ?? '')} disabled={connection !== null || connecting}>Next</button>
|
paddingLeft: '4px',
|
||||||
{connecting ? `Connecting...` : connectionError}
|
boxSizing: 'border-box',
|
||||||
<br></br>
|
}}>
|
||||||
{connection !== null && (
|
<BigButton
|
||||||
<ServerConnection url={homeServer ?? ''}>
|
icon={FaUserPlus}
|
||||||
<SignUp>
|
text="Sign up"
|
||||||
</SignUp>
|
selected={!returning}
|
||||||
</ServerConnection>
|
angle={6}
|
||||||
)}
|
width="100%"
|
||||||
{/* Create New Account!! <br />
|
inline={true}
|
||||||
Enter Home Server URL <br />
|
onClick={() => setReturning(false)}
|
||||||
<input defaultValue="wss://dev.valnet.xyz" ref={inputRef}></input> <br />
|
></BigButton>
|
||||||
<button onClick={go}> GO </button> <br />
|
</div>
|
||||||
<br />
|
</div>
|
||||||
or scan a QR! <br />
|
<Label>Home Server</Label>
|
||||||
<button onClick={scanQr}>SCAN</button><br></br>
|
<div style={{
|
||||||
<pre>
|
transform: 'skew(6deg, 0deg)',
|
||||||
{data}
|
margin: '8px',
|
||||||
{scanning ? 'SCANNING' : 'NOT SCANNING'}
|
}}>
|
||||||
</pre> */}
|
<AiOutlineEdit
|
||||||
</>
|
style={{
|
||||||
)}
|
display: homeServerHovered ? 'initial' : 'none',
|
||||||
|
float: 'right',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '8px',
|
||||||
|
right: '12px',
|
||||||
|
zIndex: '1'
|
||||||
|
}}
|
||||||
|
size={24}
|
||||||
|
></AiOutlineEdit>
|
||||||
|
<Input
|
||||||
|
hoverRef={homeServerInputRef}
|
||||||
|
disabled={!edittingHomeServer}
|
||||||
|
value={homeServerInput}
|
||||||
|
setValue={setHomeServerInput}
|
||||||
|
onKeyPress={(e: any) => e.code === 'Enter' && (setHomeServer(homeServerInput))}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<Label>Username</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={edittingHomeServer}
|
||||||
|
value={usernameInput}
|
||||||
|
setValue={setUsernameInput}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<Label>Auth Code</Label>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
}}>
|
||||||
|
<Input
|
||||||
|
disabled={edittingHomeServer}
|
||||||
|
value={authCodeInput}
|
||||||
|
setValue={setAuthCodeInput}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
transform: 'skew(6deg, 0deg)',
|
||||||
|
margin: '8px',
|
||||||
|
textAlign: 'right'
|
||||||
|
}}>
|
||||||
|
<BigButton
|
||||||
|
icon={MdOutlineNavigateNext}
|
||||||
|
text="Next"
|
||||||
|
selected={false}
|
||||||
|
angle={6}
|
||||||
|
width="auto"
|
||||||
|
inline={true}
|
||||||
|
onClick={() => {}}
|
||||||
|
></BigButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Label(props: any) {
|
||||||
|
return <label style={{
|
||||||
|
paddingLeft: '24px',
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '12px',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
}}>{props.children}</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InputProps {
|
||||||
|
value: string;
|
||||||
|
setValue: (s: string) => void;
|
||||||
|
default?: string;
|
||||||
|
onKeyPress?: (e: any) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
hoverRef?: React.LegacyRef<HTMLInputElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = (props: InputProps) => {
|
||||||
|
|
||||||
|
const _default = props.default ?? '';
|
||||||
|
const [focused, setFocused] = useState(false);
|
||||||
|
const disabled = props.disabled ?? false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
}}>
|
||||||
|
<input
|
||||||
|
ref={props.hoverRef}
|
||||||
|
onKeyPress={props.onKeyPress ?? (() => {})}
|
||||||
|
onFocus={(e) => !!props.disabled ? e.target.blur() : setFocused(true)}
|
||||||
|
onBlur={() => setFocused(false)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{
|
||||||
|
height: '40px',
|
||||||
|
width: '100%',
|
||||||
|
padding: '0px',
|
||||||
|
margin: '0px',
|
||||||
|
border: focused ? '1px solid var(--neutral-7)' : '1px solid rgba(0, 0, 0, 0)',
|
||||||
|
transform: 'skew(-6deg, 0deg)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
outline: 'none',
|
||||||
|
fontSize: '20px',
|
||||||
|
paddingLeft: '12px',
|
||||||
|
paddingRight: '12px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
background: disabled ? 'var(--neutral-3)' : focused ? 'var(--neutral-2)' : 'var(--neutral-1)',
|
||||||
|
color: disabled ? 'var(--neutral-6)' : 'var(--neutral-8)'
|
||||||
|
}}
|
||||||
|
spellCheck="false"
|
||||||
|
onChange={(e) => props.setValue(e.target.value)}
|
||||||
|
value={props.value}
|
||||||
|
></input>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SignUp = (props: any) => {
|
|
||||||
|
|
||||||
const usernameRef = useRef<HTMLInputElement>(null);
|
|
||||||
const displayNameRef = useRef<HTMLInputElement>(null);
|
|
||||||
const totpRef = useRef<HTMLInputElement>(null);
|
|
||||||
const [clientId, setClientId] = useState<string | null>(null);
|
|
||||||
// const [totpToken, setTotpToken] = useState<string | null>(null);
|
|
||||||
const [qr, setQr] = useState<string | null>(null);
|
|
||||||
const { setSessionToken } = useSessionToken();
|
|
||||||
|
|
||||||
const { send } = useApi({
|
|
||||||
'client:new'(data: any) {
|
|
||||||
setClientId(data);
|
|
||||||
},
|
|
||||||
async 'totp:propose'(data: any) {
|
|
||||||
setQr(await QR.toDataURL(
|
|
||||||
'otpauth://totp/' +
|
|
||||||
(usernameRef.current?.value ?? '') +
|
|
||||||
'?secret=' +
|
|
||||||
data +
|
|
||||||
'&issuer=valnet-corner'
|
|
||||||
));
|
|
||||||
},
|
|
||||||
'totp:confirm'(data: any) {
|
|
||||||
setSessionToken(data.token);
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
}, [setSessionToken]);
|
|
||||||
|
|
||||||
const createAccount = useCallback(() => {
|
|
||||||
send('client:new', {
|
|
||||||
username: usernameRef.current?.value,
|
|
||||||
displayName: displayNameRef.current?.value,
|
|
||||||
})
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(clientId === null) return;
|
|
||||||
send('totp:propose', clientId);
|
|
||||||
}, [clientId]);
|
|
||||||
|
|
||||||
const changeTotp = useCallback(() => {
|
|
||||||
const value = totpRef.current?.value ?? '';
|
|
||||||
if(!(/[0-9]{6}/.test(value))) return;
|
|
||||||
send('totp:confirm', {
|
|
||||||
clientId,
|
|
||||||
code: value
|
|
||||||
})
|
|
||||||
}, [clientId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<label>Username</label>
|
|
||||||
<input defaultValue={'Test' + Math.floor(Math.random() * 1000)} disabled={clientId !== null} ref={usernameRef}></input>
|
|
||||||
<label>Display Name</label>
|
|
||||||
<input defaultValue="Val" disabled={clientId !== null} ref={displayNameRef}></input>
|
|
||||||
<button disabled={clientId !== null} onClick={createAccount}>Next</button>
|
|
||||||
{clientId && (
|
|
||||||
<>
|
|
||||||
<br></br>
|
|
||||||
<img src={qr ?? ''}></img>
|
|
||||||
<br></br>
|
|
||||||
<label>TOTP Code</label>
|
|
||||||
<input onChange={changeTotp} ref={totpRef}></input>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
import { useCallback, useContext, useState } from "react";
|
||||||
|
import { MdManageAccounts } from "react-icons/md";
|
||||||
|
import TwoPanel from "../components/TwoPanel";
|
||||||
|
import { SettingsContext } from "../contexts/EphemeralState/EphemeralState";
|
||||||
|
import { AiOutlineCloseCircle } from 'react-icons/ai';
|
||||||
|
import { BiLogOut } from 'react-icons/bi';
|
||||||
|
import { useApi } from "../lib/useApi";
|
||||||
|
import useSessionToken from "../hooks/useSessionToken";
|
||||||
|
import { BigButton } from "./BigButton";
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
['General', MdManageAccounts],
|
||||||
|
['Appearance', MdManageAccounts],
|
||||||
|
['Voice & Video', MdManageAccounts],
|
||||||
|
['Notifications', MdManageAccounts],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
const { closeSettings } = useContext(SettingsContext);
|
||||||
|
const { setSessionToken } = useSessionToken()
|
||||||
|
|
||||||
|
const { send } = useApi();
|
||||||
|
|
||||||
|
const logout = useCallback(() => {
|
||||||
|
send('session:invalidate');
|
||||||
|
setSessionToken(null);
|
||||||
|
}, [send])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '32px',
|
||||||
|
right: '32px',
|
||||||
|
zIndex: '1',
|
||||||
|
display: 'flex',
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}} onClick={closeSettings}>
|
||||||
|
<AiOutlineCloseCircle
|
||||||
|
size={32}
|
||||||
|
></AiOutlineCloseCircle>
|
||||||
|
</div>
|
||||||
|
<TwoPanel
|
||||||
|
threshold={800}
|
||||||
|
sidebar={300}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--neutral-3)',
|
||||||
|
height: '100%',
|
||||||
|
marginLeft: '40%',
|
||||||
|
marginRight: '8px',
|
||||||
|
}}>
|
||||||
|
<br></br>
|
||||||
|
<br></br>
|
||||||
|
<br></br>
|
||||||
|
<br></br>
|
||||||
|
{pages.map((v, i) => (
|
||||||
|
<BigButton
|
||||||
|
key={i}
|
||||||
|
icon={pages[i][1]}
|
||||||
|
text={pages[i][0]}
|
||||||
|
selected={i === page}
|
||||||
|
onClick={() => setPage(i)}
|
||||||
|
></BigButton>
|
||||||
|
))}
|
||||||
|
<br></br>
|
||||||
|
<BigButton
|
||||||
|
icon={BiLogOut}
|
||||||
|
text="Logout"
|
||||||
|
selected={false}
|
||||||
|
color="var(--red)"
|
||||||
|
onClick={logout}
|
||||||
|
></BigButton>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--neutral-4)',
|
||||||
|
height: '100%',
|
||||||
|
paddingLeft: '32px'
|
||||||
|
}}>
|
||||||
|
<br></br>
|
||||||
|
<br></br>
|
||||||
|
{/* <br></br> */}
|
||||||
|
<div style={{
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: '12px',
|
||||||
|
}}>
|
||||||
|
{pages[page][0].toString().toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<br></br>
|
||||||
|
{(() => {
|
||||||
|
switch(page) {
|
||||||
|
case 0: return <GeneralSettings></GeneralSettings>
|
||||||
|
default: return <GeneralSettings></GeneralSettings>
|
||||||
|
}
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
</TwoPanel>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function GeneralSettings() {
|
||||||
|
return (
|
||||||
|
<div>THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE THIS IS A PAGE </div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useCallback, useRef } from "react";
|
||||||
|
import { useApi } from "../lib/useApi";
|
||||||
|
import QR from 'qrcode';
|
||||||
|
import useSessionToken from "../hooks/useSessionToken";
|
||||||
|
|
||||||
|
export const SignUp = (props: any) => {
|
||||||
|
|
||||||
|
const usernameRef = useRef<HTMLInputElement>(null);
|
||||||
|
const displayNameRef = useRef<HTMLInputElement>(null);
|
||||||
|
const totpRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [clientId, setClientId] = useState<string | null>(null);
|
||||||
|
// const [totpToken, setTotpToken] = useState<string | null>(null);
|
||||||
|
const [qr, setQr] = useState<string | null>(null);
|
||||||
|
const { setSessionToken } = useSessionToken();
|
||||||
|
|
||||||
|
const { send } = useApi({
|
||||||
|
'client:new'(data: any) {
|
||||||
|
setClientId(data);
|
||||||
|
},
|
||||||
|
async 'totp:propose'(data: any) {
|
||||||
|
setQr(await QR.toDataURL(
|
||||||
|
'otpauth://totp/' +
|
||||||
|
(usernameRef.current?.value ?? '') +
|
||||||
|
'?secret=' +
|
||||||
|
data +
|
||||||
|
'&issuer=valnet-corner'
|
||||||
|
));
|
||||||
|
},
|
||||||
|
'totp:confirm'(data: any) {
|
||||||
|
setSessionToken(data.token);
|
||||||
|
console.log(data);
|
||||||
|
}
|
||||||
|
}, [setSessionToken]);
|
||||||
|
|
||||||
|
const createAccount = useCallback(() => {
|
||||||
|
send('client:new', {
|
||||||
|
username: usernameRef.current?.value,
|
||||||
|
displayName: displayNameRef.current?.value,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (clientId === null)
|
||||||
|
return;
|
||||||
|
send('totp:propose', clientId);
|
||||||
|
}, [clientId]);
|
||||||
|
|
||||||
|
const changeTotp = useCallback(() => {
|
||||||
|
const value = totpRef.current?.value ?? '';
|
||||||
|
if (!(/[0-9]{6}/.test(value)))
|
||||||
|
return;
|
||||||
|
send('totp:confirm', {
|
||||||
|
clientId,
|
||||||
|
code: value
|
||||||
|
});
|
||||||
|
}, [clientId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<label>Username</label>
|
||||||
|
<input defaultValue={'Test' + Math.floor(Math.random() * 1000)} disabled={clientId !== null} ref={usernameRef}></input>
|
||||||
|
<label>Display Name</label>
|
||||||
|
<input defaultValue="Val" disabled={clientId !== null} ref={displayNameRef}></input>
|
||||||
|
<button disabled={clientId !== null} onClick={createAccount}>Next</button>
|
||||||
|
{clientId && (
|
||||||
|
<>
|
||||||
|
<br></br>
|
||||||
|
<img src={qr ?? ''}></img>
|
||||||
|
<br></br>
|
||||||
|
<label>TOTP Code</label>
|
||||||
|
<input onChange={changeTotp} ref={totpRef}></input>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -17,6 +17,8 @@ const api = router({
|
||||||
client: client,
|
client: client,
|
||||||
clients: client,
|
clients: client,
|
||||||
totp: totp,
|
totp: totp,
|
||||||
|
session: session,
|
||||||
|
sessions: session,
|
||||||
});
|
});
|
||||||
|
|
||||||
expose(api, 3000);
|
expose(api, 3000);
|
||||||
|
|
@ -24,6 +26,7 @@ expose(api, 3000);
|
||||||
// -------------
|
// -------------
|
||||||
|
|
||||||
import { update } from './db/migrate';
|
import { update } from './db/migrate';
|
||||||
|
import session from './routers/session';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
update();
|
update();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export function expose(router: Function, port: number) {
|
||||||
try {
|
try {
|
||||||
if(typeof data === 'object' && 'sessionToken' in data) {
|
if(typeof data === 'object' && 'sessionToken' in data) {
|
||||||
const auth = await validateSessionToken(data.sessionToken);
|
const auth = await validateSessionToken(data.sessionToken);
|
||||||
|
data.$sessionToken = data.sessionToken;
|
||||||
delete data['sessionToken'];
|
delete data['sessionToken'];
|
||||||
if(auth === null) return;
|
if(auth === null) return;
|
||||||
data.$clientId = auth;
|
data.$clientId = auth;
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import _get from '../db/snippets/session/get.sql'
|
||||||
import query from "../db/query";
|
import query from "../db/query";
|
||||||
|
|
||||||
export default router({
|
export default router({
|
||||||
async 'invalidate'(token: string) {
|
async 'invalidate'(data: any) {
|
||||||
await query(invalidate, token);
|
await query(invalidate, data.$sessionToken);
|
||||||
return reply({
|
return reply({
|
||||||
err: null
|
err: null
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Reference in New Issue