aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/App.js38
-rw-r--r--components/Form.js46
-rw-r--r--components/Header.js132
-rw-r--r--components/Layout.js67
-rw-r--r--components/Popup.js32
-rw-r--r--components/index.js4
6 files changed, 319 insertions, 0 deletions
diff --git a/components/App.js b/components/App.js
new file mode 100644
index 0000000..02443f3
--- /dev/null
+++ b/components/App.js
@@ -0,0 +1,38 @@
+import React, {useState, useEffect, useRef} from 'react'
+import useUser from 'lib/useUser'
+import fetchJson from 'lib/fetchJson'
+import {close, toggleMin, toggleMax, move} from 'helpers/windowActions'
+import {Layout} from 'components'
+
+const App = ({children, app, apps, setApps}) => {
+ const winRef = useRef(null);
+ const [errorMsg, setErrorMsg] = useState('')
+
+ useEffect(() => {
+ move(app.name, winRef, apps, setApps)
+ }, [])
+
+ return (
+ <>
+ <div
+ ref={winRef}
+ className={
+ 'list window'
+ + (app.min ? ' hidden' : '')
+ + (app.max ? ' maximized' : '')
+ }
+ style={app.pos.length ? {top: app.pos[1], left: app.pos[0]} : {}}
+ >
+ <div className='window__title'>Notes</div>
+ <div className='window__title-buttons'>
+ <span onClick={() => toggleMin('Notes', apps, setApps)}>_</span>
+ <span onClick={() => toggleMax('Notes', apps, setApps)}>+</span>
+ <span onClick={() => close('Notes', apps, setApps)}>x</span>
+ </div>
+ <div className='window__content'>{children}</div>
+ </div>
+ </>
+ )
+}
+
+export default App;
diff --git a/components/Form.js b/components/Form.js
new file mode 100644
index 0000000..fc14a75
--- /dev/null
+++ b/components/Form.js
@@ -0,0 +1,46 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+
+const Form = ({errorMessage, onSubmit, isLogin}) => (
+ <form className='window window--popup' onSubmit={onSubmit}>
+ <div className="window__content--popup">
+ {isLogin ? 'Login to access your notes' : 'Register new user'}
+ </div>
+ <input type="email" name="email" placeholder="email" required />
+ <input type="password" name="password" minLength="6" placeholder="password" required />
+
+ <input className='window__button' type="submit" value={isLogin ? 'Login' : 'Register'} />
+
+ {errorMessage && <p className="error">{errorMessage}</p>}
+
+ <style jsx>{`
+ form,
+ label {
+ display: flex;
+ flex-flow: column;
+ }
+ label > span {
+ font-weight: 600;
+ }
+ input[type=email],
+ input[type=password] {
+ padding: .5em;
+ margin: .5em 0;
+ border: 1px solid #ccc;
+ border-radius: .25px;
+ }
+ .error {
+ text-align: center;
+ color: brown;
+ margin: 1rem 0 0;
+ }
+ `}</style>
+ </form>
+)
+
+export default Form
+
+Form.propTypes = {
+ errorMessage: PropTypes.string,
+ onSubmit: PropTypes.func,
+}
diff --git a/components/Header.js b/components/Header.js
new file mode 100644
index 0000000..4302af9
--- /dev/null
+++ b/components/Header.js
@@ -0,0 +1,132 @@
+import React, {useState} from 'react'
+import Link from 'next/link'
+import useUser from 'lib/useUser'
+import fetchJson from 'lib/fetchJson'
+import {toggleMin} from 'helpers/windowActions'
+import {useRouter} from 'next/router'
+
+const Header = ({apps, setApps}) => {
+ const [userMenu, setUserMenu] = useState(false);
+ const {user, mutateUser} = useUser()
+ const router = useRouter()
+
+ const handleLogout = async (e) => {
+ e.preventDefault()
+ mutateUser(
+ await fetchJson('/api/logout', {method: 'POST'}),
+ false
+ )
+ router.push('/login')
+ }
+
+ return (
+ <header>
+ <nav>
+ <ul className='header__apps'>
+ {
+ apps && apps.map(app => (
+ <li
+ key={app.name}
+ style={app.min ? {fontWeight: 600} : {}}
+ onClick={() => toggleMin(app.name, apps, setApps)}
+ >
+ {app.name}
+ </li>
+ ))
+ }
+ </ul>
+ <ul>
+ {!user?.isLoggedIn && (
+ <li>
+ <Link href="/register">
+ <a>Register</a>
+ </Link>
+ </li>
+ )}
+ {!user?.isLoggedIn && (
+ <li>
+ <Link href="/login">
+ <a>Login</a>
+ </Link>
+ </li>
+ )}
+ {user?.isLoggedIn && (
+ <li>
+ <p
+ className='user-menu'
+ onClick={() => setUserMenu(!userMenu)}
+ >{user?.email}</p>
+ {
+ userMenu && (
+ <ul className='user-submenu'>
+ <li>
+ <a href="/api/logout" onClick={handleLogout}>
+ Logout
+ </a>
+ </li>
+ </ul>
+ )
+ }
+ </li>
+ )}
+ </ul>
+ </nav>
+ <style jsx>{`
+ header {
+ height: 2em;
+ padding: 0.5rem;
+ background-color: rgba(255, 255, 255, 0.4);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.5)
+ }
+
+ nav {
+ display: flex;
+ }
+
+ .header__apps {
+ flex-grow: 1;
+ overflow: auto;
+ }
+
+ li {
+ margin-left: 1em;
+ margin-right: 1em;
+ display: inline-block;
+ cursor: pointer;
+ }
+
+ .header__separator {
+ margin-right: auto;
+ }
+
+ a {
+ color: #333;
+ font-weight: 600;
+ text-decoration: none;
+ align-items: center;
+ }
+
+ .user-menu {
+ font-weight: 600;
+ cursor: pointer;
+ position: relative;
+ }
+
+ .user-submenu {
+ position: absolute;
+ right: 0;
+ top: 2.1em;
+ width: 10em;
+ padding: .5em;
+ background-color: rgba(255, 255, 255, .9);
+ border: 1px solid rgba(255, 255, 255, .5)
+ }
+ .user-submenu a {
+ text-align: right;
+ }
+ `}</style>
+ </header>
+ )
+}
+
+export default Header
diff --git a/components/Layout.js b/components/Layout.js
new file mode 100644
index 0000000..f77ea0c
--- /dev/null
+++ b/components/Layout.js
@@ -0,0 +1,67 @@
+import React, {useState} from 'react'
+import Head from 'next/head'
+import Context from '../context';
+import Header from './Header'
+import Popup from './Popup'
+import PropTypes from 'prop-types'
+
+const Layout = ({ children, apps, setApps}) => {
+ const [popup, setPopup] = useState({})
+
+ return (
+ <Context.Provider value={{ setPopup }}>
+ <Head>
+ <title>Notes App</title>
+ </Head>
+ <main>
+ <div className="container">{children}</div>
+ </main>
+ <Header apps={apps} setApps={setApps} />
+ <Popup popup={popup} />
+ <style jsx global>{`
+ main {
+ position: fixed;
+ top: 2em;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ }
+
+ body {
+ margin: 0;
+ color: #222;
+ height: 100vh;
+ overflow: hidden;
+
+ background: #50a3a2;
+ background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
+ background: -moz-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
+ background: -o-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%);
+ background: linear-gradient(to bottom right, #50a3a2 0%, #53e3a6 100%);
+
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Helvetica Neue', Arial, Noto Sans, sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ }
+
+ textarea, input {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
+ 'Helvetica Neue', Arial, Noto Sans, sans-serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ }
+
+ .container {
+ margin: 1.5rem auto;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ `}</style>
+ </Context.Provider>
+ )
+}
+
+export default Layout
+
+Layout.propTypes = {
+ children: PropTypes.node,
+}
diff --git a/components/Popup.js b/components/Popup.js
new file mode 100644
index 0000000..911d2fa
--- /dev/null
+++ b/components/Popup.js
@@ -0,0 +1,32 @@
+import React, {useState, useEffect} from 'react'
+
+const Popup = ({popup}) => {
+ const [visible, setVisible] = useState(false)
+ const {
+ content = null,
+ time = null,
+ error = null,
+ yes = null,
+ no = null,
+ } = popup
+
+ useEffect(() => {
+ setVisible(true)
+ time && setTimeout(() => setVisible(false), time)
+ }, [popup])
+
+ if (!content) return null
+
+ return visible ? (
+ <div className={`window window--popup${error ? ' window--error' : ''}`}>
+ <div className="window__content--popup">{ content }</div>
+ {
+ (yes || no) && (<div className="window__buttons--popup">
+ {[yes, no].map(a => a && <input key={a.label} className='window__button' type="button" onClick={() => { setVisible(false); a.action() }} value={a.label} />)}
+ </div>)
+ }
+ </div>
+ ) : null
+}
+
+export default Popup
diff --git a/components/index.js b/components/index.js
new file mode 100644
index 0000000..4875559
--- /dev/null
+++ b/components/index.js
@@ -0,0 +1,4 @@
+export {default as Form} from './Form'
+export {default as Header} from './Header'
+export {default as Layout} from './Layout'
+export {default as App} from './App'