mirror of
https://github.com/maelgangloff/domain-watchdog.git
synced 2025-12-29 16:15:04 +00:00
feat: update front
This commit is contained in:
@@ -1,47 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import {PaletteMode} from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Button from '@mui/material/Button';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Container from '@mui/material/Container';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import ToggleColorMode from './ToggleColorMode';
|
||||
|
||||
import Sitemark from './SitemarkIcon';
|
||||
import {NavLink} from "react-router-dom";
|
||||
import ToggleColorMode from "./ToggleColorMode";
|
||||
import {PaletteMode} from "@mui/material";
|
||||
import Link from "@mui/material/Link";
|
||||
|
||||
interface AppAppBarProps {
|
||||
mode: PaletteMode;
|
||||
toggleColorMode: () => void;
|
||||
isAuthenticated: boolean
|
||||
}
|
||||
|
||||
export default function AppAppBar({mode, toggleColorMode}: AppAppBarProps) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const toggleDrawer = (newOpen: boolean) => () => {
|
||||
setOpen(newOpen);
|
||||
};
|
||||
|
||||
const scrollToSection = (sectionId: string) => {
|
||||
const sectionElement = document.getElementById(sectionId);
|
||||
const offset = 128;
|
||||
if (sectionElement) {
|
||||
const targetScroll = sectionElement.offsetTop - offset;
|
||||
sectionElement.scrollIntoView({behavior: 'smooth'});
|
||||
window.scrollTo({
|
||||
top: targetScroll,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
export default function AppAppBar({mode, toggleColorMode, isAuthenticated}: AppAppBarProps) {
|
||||
return (
|
||||
<AppBar
|
||||
position="fixed"
|
||||
@@ -73,23 +49,26 @@ export default function AppAppBar({mode, toggleColorMode}: AppAppBarProps) {
|
||||
<Box sx={{flexGrow: 1, display: 'flex', alignItems: 'center', px: 0}}>
|
||||
<Sitemark/>
|
||||
<Box sx={{display: {xs: 'none', md: 'flex'}}}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
size="small"
|
||||
onClick={() => scrollToSection('highlights')}
|
||||
>
|
||||
Highlights
|
||||
</Button>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
size="small"
|
||||
onClick={() => scrollToSection('faq')}
|
||||
sx={{minWidth: 0}}
|
||||
>
|
||||
FAQ
|
||||
</Button>
|
||||
<NavLink to='/'>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
size="small"
|
||||
>
|
||||
Presentation
|
||||
</Button>
|
||||
</NavLink>
|
||||
</Box>
|
||||
<Box sx={{display: {xs: 'none', md: 'flex'}}}>
|
||||
<NavLink to="/dashboard">
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
size="small"
|
||||
>
|
||||
Dashboard
|
||||
</Button>
|
||||
</NavLink>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
@@ -104,42 +83,19 @@ export default function AppAppBar({mode, toggleColorMode}: AppAppBarProps) {
|
||||
mode={mode}
|
||||
toggleColorMode={toggleColorMode}
|
||||
/>
|
||||
<NavLink to="/login">
|
||||
<Button color="primary" variant="text" size="small">
|
||||
Sign in
|
||||
</Button>
|
||||
</NavLink>
|
||||
</Box>
|
||||
<Box sx={{display: {sm: 'flex', md: 'none'}}}>
|
||||
<IconButton aria-label="Menu button" onClick={toggleDrawer(true)}>
|
||||
<MenuIcon/>
|
||||
</IconButton>
|
||||
<Drawer anchor="top" open={open} onClose={toggleDrawer(false)}>
|
||||
<Box sx={{p: 2, backgroundColor: 'background.default'}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<ToggleColorMode mode={mode} toggleColorMode={toggleColorMode}/>
|
||||
<IconButton onClick={toggleDrawer(false)}>
|
||||
<CloseRoundedIcon/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Divider sx={{my: 3}}/>
|
||||
<MenuItem onClick={() => scrollToSection('highlights')}>
|
||||
Highlights
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => scrollToSection('faq')}>FAQ</MenuItem>
|
||||
<MenuItem>
|
||||
<Button color="primary" variant="outlined" fullWidth>
|
||||
{
|
||||
!isAuthenticated ?
|
||||
<NavLink to="/login">
|
||||
<Button color="primary" variant="text" size="small">
|
||||
Sign in
|
||||
</Button>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
</Drawer>
|
||||
</NavLink>
|
||||
: <Link href="/logout">
|
||||
<Button color="primary" variant="text" size="small">
|
||||
Log out
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
|
||||
@@ -5,8 +5,7 @@ import IconButton from '@mui/material/IconButton';
|
||||
import Link from '@mui/material/Link';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import FacebookIcon from '@mui/icons-material/GitHub';
|
||||
import {NavLink} from "react-router-dom";
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
@@ -41,15 +40,19 @@ export default function Footer() {
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Link color="text.secondary" variant="body2" href="#">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<NavLink to="/privacy">
|
||||
<Link color="text.secondary" variant="body2">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</NavLink>
|
||||
<Typography sx={{display: 'inline', mx: 0.5, opacity: 0.5}}>
|
||||
•
|
||||
</Typography>
|
||||
<Link color="text.secondary" variant="body2" href="#">
|
||||
Terms of Service
|
||||
</Link>
|
||||
<NavLink to="/tos">
|
||||
<Link color="text.secondary" variant="body2">
|
||||
Terms of Service
|
||||
</Link>
|
||||
</NavLink>
|
||||
<Copyright/>
|
||||
</div>
|
||||
<Stack
|
||||
@@ -64,7 +67,6 @@ export default function Footer() {
|
||||
aria-label="GitHub"
|
||||
sx={{alignSelf: 'center'}}
|
||||
>
|
||||
<FacebookIcon/>
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
1
assets/content/.gitignore
vendored
Normal file
1
assets/content/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.md
|
||||
4
assets/declaration.d.ts
vendored
Normal file
4
assets/declaration.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.md" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,18 +1,54 @@
|
||||
import React from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
|
||||
import LandingPage from "./pages/LandingPage";
|
||||
import {Route, Routes, HashRouter} from "react-router-dom";
|
||||
import FAQ from "./components/FAQ";
|
||||
import TextPage from "./pages/TextPage";
|
||||
import {HashRouter, Route, Routes} from "react-router-dom";
|
||||
|
||||
import tosContent from "./content/tos.md"
|
||||
import privacyContent from "./content/privacy.md"
|
||||
import LoginPage from "./pages/LoginPage";
|
||||
import {createTheme, PaletteMode, ThemeProvider} from "@mui/material";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import AppAppBar from "./components/AppAppBar";
|
||||
import DashboardPage from "./pages/DashboardPage";
|
||||
import {getUser} from "./utils/api";
|
||||
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage/>}/>
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
|
||||
|
||||
function App() {
|
||||
const [mode, setMode] = React.useState<PaletteMode>('dark')
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
|
||||
const toggleColorMode = () => {
|
||||
setMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUser().then(() => setIsAuthenticated(true)).catch(() => setIsAuthenticated(false))
|
||||
}, []);
|
||||
|
||||
return <React.StrictMode>
|
||||
<ThemeProvider theme={createTheme({palette: {mode: mode}})}>
|
||||
<HashRouter>
|
||||
<CssBaseline/>
|
||||
<AppAppBar mode={mode} toggleColorMode={toggleColorMode} isAuthenticated={isAuthenticated}/>
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage/>}/>
|
||||
<Route path="/tos" element={<TextPage content={tosContent}/>}/>
|
||||
<Route path="/privacy" element={<TextPage content={privacyContent}/>}/>
|
||||
{isAuthenticated ?
|
||||
<Route path="/dashboard" element={<DashboardPage/>}/>
|
||||
:
|
||||
<Route path="*" element={<LoginPage isAuthenticated={isAuthenticated}
|
||||
setIsAuthenticated={setIsAuthenticated}/>}/>
|
||||
}
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
root.render(<App/>)
|
||||
|
||||
12
assets/pages/DashboardPage.tsx
Normal file
12
assets/pages/DashboardPage.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
|
||||
const DashboardPage = () => {
|
||||
return (
|
||||
<>
|
||||
<p>Dashboard</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardPage;
|
||||
@@ -1,32 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import {PaletteMode} from '@mui/material';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Box from '@mui/material/Box';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import {createTheme, ThemeProvider} from '@mui/material/styles';
|
||||
import AppAppBar from '../components/AppAppBar';
|
||||
import Hero from '../components/Hero';
|
||||
import Highlights from '../components/Highlights';
|
||||
import FAQ from '../components/FAQ';
|
||||
import Footer from '../components/Footer';
|
||||
|
||||
interface ToggleCustomThemeProps {
|
||||
showCustomTheme: Boolean;
|
||||
toggleCustomTheme: () => void;
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const [mode, setMode] = React.useState<PaletteMode>('light');
|
||||
const defaultTheme = createTheme({palette: {mode}});
|
||||
|
||||
const toggleColorMode = () => {
|
||||
setMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={defaultTheme}>
|
||||
<CssBaseline/>
|
||||
<AppAppBar mode={mode} toggleColorMode={toggleColorMode}/>
|
||||
<>
|
||||
<Hero/>
|
||||
<Box sx={{bgcolor: 'background.default'}}>
|
||||
<Divider/>
|
||||
@@ -36,6 +18,6 @@ export default function Index() {
|
||||
<Divider/>
|
||||
<Footer/>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
102
assets/pages/LoginPage.tsx
Normal file
102
assets/pages/LoginPage.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as React from 'react';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Box from '@mui/material/Box';
|
||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Container from '@mui/material/Container';
|
||||
import Footer from "../components/Footer";
|
||||
import Link from "@mui/material/Link";
|
||||
import {login} from "../utils/api";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
|
||||
interface Props {
|
||||
setIsAuthenticated: (val: boolean) => void
|
||||
isAuthenticated: boolean
|
||||
}
|
||||
|
||||
export default function LoginPage({setIsAuthenticated, isAuthenticated}: Props) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
const data = new FormData(event.currentTarget);
|
||||
|
||||
try {
|
||||
await login(data.get('email') as string, data.get('password') as string);
|
||||
setIsAuthenticated(true)
|
||||
|
||||
navigate('/dashboard');
|
||||
|
||||
} catch (e) {
|
||||
//TODO: handle error
|
||||
setIsAuthenticated(false)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container component="main" maxWidth="xs">
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 20,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Avatar sx={{m: 1, bgcolor: 'secondary.main'}}>
|
||||
<LockOutlinedIcon/>
|
||||
</Avatar>
|
||||
<Typography component="h1" variant="h5">
|
||||
Sign in
|
||||
</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit} noValidate sx={{mt: 1}}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{mt: 3}}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
<Link href="/login/oauth">
|
||||
<Button
|
||||
type="button"
|
||||
fullWidth
|
||||
color='secondary'
|
||||
variant="contained"
|
||||
sx={{mt: 3, mb: 2}}
|
||||
>
|
||||
Single Sign-On
|
||||
</Button>
|
||||
</Link>
|
||||
<Footer/>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
38
assets/pages/TextPage.tsx
Normal file
38
assets/pages/TextPage.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Container from "@mui/material/Container";
|
||||
|
||||
interface Props {
|
||||
content: string
|
||||
}
|
||||
|
||||
export default function Index({content}: Props) {
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: {xs: 4, sm: 8},
|
||||
py: {xs: 8, sm: 10},
|
||||
textAlign: {sm: 'center', md: 'left'},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
pt: {xs: 4, sm: 8},
|
||||
width: '100%',
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<div dangerouslySetInnerHTML={{__html: content}}></div>
|
||||
</Box>
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
31
assets/utils/api.ts
Normal file
31
assets/utils/api.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import axios, {AxiosRequestConfig, AxiosResponse} from "axios";
|
||||
|
||||
|
||||
export async function login(email: string, password: string): Promise<boolean> {
|
||||
const response = await request({
|
||||
method: 'POST',
|
||||
url: 'login',
|
||||
data: {email, password}
|
||||
})
|
||||
return response.status === 200
|
||||
}
|
||||
|
||||
export async function getUser(): Promise<object> {
|
||||
const response = await request({
|
||||
url: 'me'
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
async function request<T = any, R = AxiosResponse<T>, D = any>(config: AxiosRequestConfig): Promise<R> {
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
...config,
|
||||
baseURL: '/api',
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
Accept: 'application/ld+json'
|
||||
}
|
||||
}
|
||||
return await axios.request<T, any, D>(axiosConfig)
|
||||
}
|
||||
Reference in New Issue
Block a user