Far superior header
This commit is contained in:
parent
41f0dd5c7a
commit
6c8e7933e1
15 changed files with 240 additions and 72 deletions
|
@ -5,7 +5,7 @@ const Link = (props: LinkProps) => {
|
||||||
const { basePath } = useRouter();
|
const { basePath } = useRouter();
|
||||||
const propHrefWithoutLeadingSlash = props.href && props.href.startsWith("/") ? props.href.substr(1) : props.href;
|
const propHrefWithoutLeadingSlash = props.href && props.href.startsWith("/") ? props.href.substr(1) : props.href;
|
||||||
const href = basePath ? `${basePath}/${propHrefWithoutLeadingSlash}` : props.href;
|
const href = basePath ? `${basePath}/${propHrefWithoutLeadingSlash}` : props.href;
|
||||||
console.log(href)
|
(href)
|
||||||
return <GeistLink {...props} href={href} />
|
return <GeistLink {...props} href={href} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
client/components/header/header.module.css
Normal file
33
client/components/header/header.module.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.tabs {
|
||||||
|
flex: 1 1;
|
||||||
|
padding: 0 var(--gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs .current {
|
||||||
|
border-bottom: 2px solid initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs :global(.content) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.tabs {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
flex: 1 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls :global(.menu-toggle) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
|
@ -1,34 +1,161 @@
|
||||||
import { Page, ButtonGroup, Button } from "@geist-ui/core";
|
import { Page, ButtonGroup, Button, useBodyScroll, useMediaQuery, useTheme, Tabs, Loading } from "@geist-ui/core";
|
||||||
import { Moon, Sun } from "@geist-ui/icons";
|
import { Moon, Sun, UserPlus as SignUpIcon, User as SignInIcon, Home as HomeIcon, Menu as MenuIcon, Tool as SettingsIcon, UserX as SignoutIcon, PlusCircle as NewIcon, List as YourIcon } from "@geist-ui/icons";
|
||||||
import { DriftProps } from "../../pages/_app";
|
import { DriftProps } from "../../pages/_app";
|
||||||
import ShiftBy from "../shift-by";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import Link from '../Link'
|
import styles from './header.module.css';
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import useSignedIn from "../../lib/hooks/use-signed-in";
|
||||||
|
|
||||||
const Header = ({ theme, changeTheme }: DriftProps) => {
|
|
||||||
return (
|
const Header = ({ changeTheme }: DriftProps) => {
|
||||||
<Page.Header height={'40px'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"} >
|
const router = useRouter();
|
||||||
<ButtonGroup>
|
const [selectedTab, setSelectedTab] = useState<string>();
|
||||||
<Button onClick={() => {
|
const [expanded, setExpanded] = useState<boolean>(false)
|
||||||
|
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
|
||||||
|
const isMobile = useMediaQuery('xs', { match: 'down' })
|
||||||
|
const { isLoading, isSignedIn } = useSignedIn({ redirectIfNotAuthed: false })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setBodyHidden(expanded)
|
||||||
|
}, [expanded, setBodyHidden])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isMobile) {
|
||||||
|
setExpanded(false)
|
||||||
|
}
|
||||||
|
}, [isMobile])
|
||||||
|
const pages = useMemo(() => [
|
||||||
|
{
|
||||||
|
name: "Home",
|
||||||
|
href: "/",
|
||||||
|
icon: <HomeIcon />,
|
||||||
|
condition: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "New",
|
||||||
|
href: "/new",
|
||||||
|
icon: <NewIcon />,
|
||||||
|
condition: isSignedIn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Yours",
|
||||||
|
href: "/mine",
|
||||||
|
icon: <YourIcon />,
|
||||||
|
condition: isSignedIn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Settings",
|
||||||
|
href: "/",
|
||||||
|
icon: <SettingsIcon />,
|
||||||
|
condition: isSignedIn
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sign out",
|
||||||
|
action: () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
}}><Link href="/signin">Sign out</Link></Button>
|
router.push("/signin");
|
||||||
<Button>
|
}
|
||||||
{/* TODO: Link outside Button, but seems to break ButtonGroup */}
|
},
|
||||||
<Link href="/mine">
|
icon: <SignoutIcon />,
|
||||||
Yours
|
condition: isSignedIn
|
||||||
</Link>
|
},
|
||||||
</Button>
|
{
|
||||||
<Button>
|
name: "Sign in",
|
||||||
{/* TODO: Link outside Button, but seems to break ButtonGroup */}
|
href: "/signin",
|
||||||
<Link href="/new">
|
icon: <SignInIcon />,
|
||||||
New
|
condition: !isSignedIn
|
||||||
</Link>
|
},
|
||||||
</Button>
|
{
|
||||||
<Button onClick={() => changeTheme()}>
|
name: "Sign up",
|
||||||
<ShiftBy y={6}>{theme === 'light' ? <Moon /> : <Sun />}</ShiftBy>
|
href: "/signup",
|
||||||
</Button>
|
icon: <SignUpIcon />,
|
||||||
</ButtonGroup>
|
condition: !isSignedIn
|
||||||
|
}
|
||||||
|
], [isSignedIn, router])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedTab(pages.find((page) => {
|
||||||
|
if (page.href && page.href === router.asPath) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})?.href)
|
||||||
|
}, [pages, router, router.pathname])
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Page.Header height={'var(--page-nav-height)'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"} >
|
||||||
|
|
||||||
|
</Page.Header>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page.Header height={'var(--page-nav-height)'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"} >
|
||||||
|
<div className={styles.tabs}>
|
||||||
|
<Tabs
|
||||||
|
value={selectedTab}
|
||||||
|
leftSpace={0}
|
||||||
|
activeClassName={styles.current}
|
||||||
|
align="center"
|
||||||
|
hideDivider
|
||||||
|
hideBorder
|
||||||
|
onChange={(tab) => {
|
||||||
|
const nameMatch = pages.find(page => page.name === tab)
|
||||||
|
if (nameMatch?.action) {
|
||||||
|
nameMatch.action()
|
||||||
|
} else {
|
||||||
|
router.push(`${tab}`)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{pages.map((tab, index) => {
|
||||||
|
console.log(tab, tab.condition)
|
||||||
|
if (tab.condition)
|
||||||
|
return <Tabs.Item
|
||||||
|
font="14px"
|
||||||
|
label={<>{tab.icon} {tab.name}</>}
|
||||||
|
value={tab.href || tab.name}
|
||||||
|
key={`${tab.name}-${index}`}
|
||||||
|
/>
|
||||||
|
else return null
|
||||||
|
})}
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
<div className="controls">
|
||||||
|
{isMobile ? (
|
||||||
|
<Button
|
||||||
|
className="menu-toggle"
|
||||||
|
auto
|
||||||
|
type="abort"
|
||||||
|
onClick={() => setExpanded(!expanded)}>
|
||||||
|
<MenuIcon size="1.125rem" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
// <Controls />
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Page.Header >
|
</Page.Header >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Header
|
export default Header
|
||||||
|
|
||||||
|
|
||||||
|
// {/* {/* <ButtonGroup>
|
||||||
|
// <Button onClick={() => {
|
||||||
|
|
||||||
|
// }}><Link href="/signin">Sign out</Link></Button>
|
||||||
|
// <Button>
|
||||||
|
// <Link href="/mine">
|
||||||
|
// Yours
|
||||||
|
// </Link>
|
||||||
|
// </Button>
|
||||||
|
// <Button>
|
||||||
|
// {/* TODO: Link outside Button, but seems to break ButtonGroup */}
|
||||||
|
// <Link href="/new">
|
||||||
|
// New
|
||||||
|
// </Link>
|
||||||
|
// </Button >
|
||||||
|
// <Button onClick={() => changeTheme()}>
|
||||||
|
// <ShiftBy y={6}>{theme.type === 'light' ? <Moon /> : <Sun />}</ShiftBy>
|
||||||
|
// </Button>
|
||||||
|
// </ButtonGroup > * /}
|
|
@ -1,21 +0,0 @@
|
||||||
import { Page, ButtonGroup, Button } from "@geist-ui/core";
|
|
||||||
import { Moon, Sun } from "@geist-ui/icons";
|
|
||||||
import { DriftProps } from "../../pages/_app";
|
|
||||||
import ShiftBy from "../shift-by";
|
|
||||||
import Link from '../Link'
|
|
||||||
|
|
||||||
const UnauthenticatedHeader = ({ theme, changeTheme }: DriftProps) => {
|
|
||||||
return (
|
|
||||||
<Page.Header height={'40px'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"} >
|
|
||||||
<ButtonGroup>
|
|
||||||
<Button><Link href="/signup">Sign up</Link></Button>
|
|
||||||
<Button><Link href="/signin">Sign in</Link></Button>
|
|
||||||
<Button onClick={() => changeTheme()}>
|
|
||||||
<ShiftBy y={6}>{theme === 'light' ? <Moon /> : <Sun />}</ShiftBy>
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Page.Header >
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UnauthenticatedHeader
|
|
|
@ -5,7 +5,6 @@ const useSignedIn = ({ redirectIfNotAuthed = false }: { redirectIfNotAuthed?: bo
|
||||||
const [isSignedIn, setSignedIn] = useState(false)
|
const [isSignedIn, setSignedIn] = useState(false)
|
||||||
const [isLoading, setLoading] = useState(true)
|
const [isLoading, setLoading] = useState(true)
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
console.log(isSignedIn, isLoading, redirectIfNotAuthed)
|
|
||||||
if (redirectIfNotAuthed && !isLoading && isSignedIn === false) {
|
if (redirectIfNotAuthed && !isLoading && isSignedIn === false) {
|
||||||
router.push('/signin')
|
router.push('/signin')
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev --port 3001",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
|
|
@ -16,10 +16,11 @@ export type DriftProps = ThemeProps
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps<ThemeProps>) {
|
function MyApp({ Component, pageProps }: AppProps<ThemeProps>) {
|
||||||
const [themeType, setThemeType] = useState<string>(typeof window !== 'undefined' ? localStorage.getItem('drift-theme') || 'light' : 'light')
|
const [themeType, setThemeType] = useState<string>(typeof window !== 'undefined' ? localStorage.getItem('drift-theme') || 'light' : 'light')
|
||||||
|
|
||||||
const changeTheme = () => {
|
const changeTheme = () => {
|
||||||
const newTheme = themeType === 'dark' ? 'light' : 'dark'
|
const newTheme = themeType === 'dark' ? 'light' : 'dark'
|
||||||
localStorage.setItem('drift-theme', newTheme)
|
localStorage.setItem('drift-theme', newTheme)
|
||||||
setThemeType(newTheme)
|
setThemeType(last => (last === 'dark' ? 'light' : 'dark'))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
42
client/pages/index.tsx
Normal file
42
client/pages/index.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import styles from '../styles/Home.module.css'
|
||||||
|
import { Page } from '@geist-ui/core'
|
||||||
|
|
||||||
|
import Header from '../components/header'
|
||||||
|
import { ThemeProps } from './_app'
|
||||||
|
import Document from '../components/document'
|
||||||
|
const Home = ({ theme, changeTheme }: ThemeProps) => {
|
||||||
|
return (
|
||||||
|
<Page className={styles.container}>
|
||||||
|
<Head>
|
||||||
|
<title>Drift</title>
|
||||||
|
<meta name="description" content="A self-hostable clone of GitHub Gist" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
</Head>
|
||||||
|
<Page.Header>
|
||||||
|
<Header theme={theme} changeTheme={changeTheme} />
|
||||||
|
</Page.Header>
|
||||||
|
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
||||||
|
<Document
|
||||||
|
editable={false}
|
||||||
|
content={
|
||||||
|
`# Welcome to Drift
|
||||||
|
### Drift is a self-hostable clone of GitHub Gist.
|
||||||
|
#### It is a simple way to share code and text snippets with your friends, with support for the following:
|
||||||
|
|
||||||
|
- Render GitHub Extended Markdown and LaTeX (including images)
|
||||||
|
- User authentication
|
||||||
|
- Private, public, and secret posts
|
||||||
|
|
||||||
|
If you need to signup, you can join at [/signup](/signup). If you're already signed in, you can create a new post by clicking the "New" button in the header.
|
||||||
|
`}
|
||||||
|
title={`Welcome to Drift`}
|
||||||
|
initialTab={`preview`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Page.Content>
|
||||||
|
</Page >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home
|
|
@ -3,7 +3,6 @@ import styles from '../styles/Home.module.css'
|
||||||
import { Page } from '@geist-ui/core'
|
import { Page } from '@geist-ui/core'
|
||||||
|
|
||||||
import Header from '../components/header'
|
import Header from '../components/header'
|
||||||
import PostList from '../components/post-list'
|
|
||||||
import useSignedIn from '../lib/hooks/use-signed-in'
|
import useSignedIn from '../lib/hooks/use-signed-in'
|
||||||
import { Loader } from '@geist-ui/icons'
|
import { Loader } from '@geist-ui/icons'
|
||||||
import MyPosts from '../components/my-posts'
|
import MyPosts from '../components/my-posts'
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useRouter } from "next/router";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import Document from '../../components/document'
|
import Document from '../../components/document'
|
||||||
import Header from "../../components/header";
|
import Header from "../../components/header";
|
||||||
import UnauthenticatedHeader from "../../components/unauthenticated-header";
|
|
||||||
import VisibilityBadge from "../../components/visibility-badge";
|
import VisibilityBadge from "../../components/visibility-badge";
|
||||||
import { ThemeProps } from "../_app";
|
import { ThemeProps } from "../_app";
|
||||||
|
|
||||||
|
@ -44,18 +43,10 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
|
||||||
fetchPost()
|
fetchPost()
|
||||||
}, [router, router.query.id])
|
}, [router, router.query.id])
|
||||||
|
|
||||||
const token = useCallback(() => {
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
return localStorage.getItem("drift-token")
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Page.Header>
|
<Page.Header>
|
||||||
{token() && <Header theme={theme} changeTheme={changeTheme} />}
|
<Header theme={theme} changeTheme={changeTheme} />
|
||||||
{!token() && <UnauthenticatedHeader theme={theme} changeTheme={changeTheme} />}
|
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
||||||
{error && <Text type="error">{error}</Text>}
|
{error && <Text type="error">{error}</Text>}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Page } from "@geist-ui/core";
|
import { Page } from "@geist-ui/core";
|
||||||
import Auth from "../components/auth";
|
import Auth from "../components/auth";
|
||||||
import UnauthenticatedHeader from "../components/unauthenticated-header";
|
import Header from "../components/header";
|
||||||
import { ThemeProps } from "./_app";
|
import { ThemeProps } from "./_app";
|
||||||
|
|
||||||
const SignIn = ({ theme, changeTheme }: ThemeProps) => (
|
const SignIn = ({ theme, changeTheme }: ThemeProps) => (
|
||||||
<Page>
|
<Page>
|
||||||
<Page.Header>
|
<Page.Header>
|
||||||
<UnauthenticatedHeader theme={theme} changeTheme={changeTheme} />
|
<Header theme={theme} changeTheme={changeTheme} />
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
||||||
<Auth page="signin" />
|
<Auth page="signin" />
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Page } from "@geist-ui/core";
|
import { Page } from "@geist-ui/core";
|
||||||
import Auth from "../components/auth";
|
import Auth from "../components/auth";
|
||||||
import UnauthenticatedHeader from "../components/unauthenticated-header";
|
import Header from "../components/header";
|
||||||
import { ThemeProps } from "./_app";
|
import { ThemeProps } from "./_app";
|
||||||
|
|
||||||
const SignUp = ({ theme, changeTheme }: ThemeProps) => (
|
const SignUp = ({ theme, changeTheme }: ThemeProps) => (
|
||||||
<Page>
|
<Page>
|
||||||
<Page.Header>
|
<Page.Header>
|
||||||
<UnauthenticatedHeader theme={theme} changeTheme={changeTheme} />
|
<Header theme={theme} changeTheme={changeTheme} />
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
<Page.Content width={"var(--main-content-width)"} margin="auto">
|
||||||
<Auth page="signup" />
|
<Auth page="signup" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
:root {
|
:root {
|
||||||
--main-content-width: 800px;
|
--main-content-width: 800px;
|
||||||
|
--page-nav-height: 60px;
|
||||||
--gap: 8px;
|
--gap: 8px;
|
||||||
--gap-half: calc(var(--gap) / 2);
|
--gap-half: calc(var(--gap) / 2);
|
||||||
--gap-double: calc(var(--gap) * 2);
|
--gap-double: calc(var(--gap) * 2);
|
||||||
|
|
|
@ -33,7 +33,6 @@ posts.post('/create', jwt, async (req, res, next) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
await newPost.save()
|
await newPost.save()
|
||||||
console.log("UserId", req.body.userId)
|
|
||||||
await newPost.$add('users', req.body.userId);
|
await newPost.$add('users', req.body.userId);
|
||||||
const newFiles = await Promise.all(req.body.files.map(async (file) => {
|
const newFiles = await Promise.all(req.body.files.map(async (file) => {
|
||||||
// Establish a "file" for each file in the request
|
// Establish a "file" for each file in the request
|
||||||
|
@ -50,7 +49,6 @@ posts.post('/create', jwt, async (req, res, next) => {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
await Promise.all(newFiles.map((file) => {
|
await Promise.all(newFiles.map((file) => {
|
||||||
console.log("FileId", file.id)
|
|
||||||
newPost.$add("files", file.id);
|
newPost.$add("files", file.id);
|
||||||
newPost.save();
|
newPost.save();
|
||||||
}))
|
}))
|
||||||
|
@ -81,7 +79,6 @@ posts.get("/:id", async (req: UserJwtRequest, res, next) => {
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(post)
|
|
||||||
if (post?.visibility === 'public' || post?.visibility === 'unlisted') {
|
if (post?.visibility === 'public' || post?.visibility === 'unlisted') {
|
||||||
res.json(post);
|
res.json(post);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -66,7 +66,6 @@ users.post('/signup', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const created_user = await User.create(user);
|
const created_user = await User.create(user);
|
||||||
console.log(user)
|
|
||||||
|
|
||||||
const token = generateAccessToken(created_user.id);
|
const token = generateAccessToken(created_user.id);
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ users.post('/login', async (req, res, next) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error("User does not exist");
|
throw new Error("User does not exist");
|
||||||
}
|
}
|
||||||
console.log(user)
|
|
||||||
const password_valid = await compare(req.body.password, user.password);
|
const password_valid = await compare(req.body.password, user.password);
|
||||||
if (password_valid) {
|
if (password_valid) {
|
||||||
const token = generateAccessToken(user.id);
|
const token = generateAccessToken(user.id);
|
||||||
|
|
Loading…
Reference in a new issue