css and some nav

This commit is contained in:
ntr committed 2022-08-10 23:47:33 +10:00
1 parent a8e5e5d7cd
commit 9a42c290ae
10 files changed
+273 -195

No files matched your search

+12 -12
View File
@@ -22,17 +22,17 @@ export default function createApi(log: Log, pg: Pg, discogs) {
const app = new koa(); const app = new koa();
const server = http.createServer(app.callback()); const server = http.createServer(app.callback());
// app.use(async (ctx, next) => { app.use(async (ctx, next) => {
// try { try {
// await next(); await next();
// } catch (err) { } catch (err) {
// log.error(err); log.error(err);
// ctx.status = err.statusCode || err.status || 500; ctx.status = err.statusCode || err.status || 500;
// ctx.body = { ctx.body = {
// message: err.message error: err.message
// }; };
// } }
// }); });
const router = koaRouter(); const router = koaRouter();
@@ -128,7 +128,7 @@ export default function createApi(log: Log, pg: Pg, discogs) {
const { id } = ctx.request.params; const { id } = ctx.request.params;
const user = await pg.userFindId(id); const user = await pg.userFindId(id);
// if (!user) return ctx.response.status = 404; ctx.assert(user, 404, `user ${id} not found`);
const bundles = (await getBundles()) const bundles = (await getBundles())
.map(b => { .map(b => {
-28
View File
@@ -1,28 +0,0 @@
import { KoaPassport } from 'koa-passport';
import { Strategy as LocalStrategy } from 'passport-local';
export default function authService(pg) {
const passport = new KoaPassport();
passport.serializeUser((user, done) => done(null, user.id));
passport.deserializeUser((id, done) => {
pg.userFindId(id)
.then(u => done(null, u))
.catch(e => done(e, null));
})
passport.use(new LocalStrategy(
{ usernameField: 'email '},
function localStrategy(email, password, done) {
pg.userFindEmail(email)
.then(user => {
if (!user) return done(null, false);
// if (!user.verifyPassword(password)) return done(null, false);
return done(null, user);
})
.catch(e => done(e, null));
}
));
return passport;
}
+4 -2
View File
@@ -38,7 +38,7 @@ export default function authRouter(log, pg: Pg) {
authRouter.post('/login', async (ctx, next) => { authRouter.post('/login', async (ctx, next) => {
await passport.authenticate('local', (err, user, info, status) => { await passport.authenticate('local', (err, user, info, status) => {
ctx.assert(user, 401, { error: 'incorrect login' }); ctx.assert(user, 401, 'incorrect login');
ctx.login(user); ctx.login(user);
ctx.status = 200; ctx.status = 200;
@@ -50,11 +50,12 @@ export default function authRouter(log, pg: Pg) {
}); });
authRouter.get('/login', async (ctx, next) => { authRouter.get('/login', async (ctx, next) => {
ctx.assert(ctx.isAuthenticated(), 401, { error: 'log in required' }); ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
const user = ctx.state.user; const user = ctx.state.user;
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
id: user.id,
email: user.email, email: user.email,
discogs: user.discogs, discogs: user.discogs,
}; };
@@ -68,6 +69,7 @@ export default function authRouter(log, pg: Pg) {
ctx.login(user); ctx.login(user);
ctx.status = 200; ctx.status = 200;
ctx.body = { ctx.body = {
id: user.id,
email: user.email, email: user.email,
discogs: user.discogs, discogs: user.discogs,
}; };
+7 -6
View File
@@ -6,22 +6,22 @@ const api = axios.create({
}); });
const actions = { const actions = {
getBundles: async () => { getBundles: async function getBundles() {
const { data: bundles } = await api.get('/bundles'); const { data: bundles } = await api.get('/bundles');
return bundles; return bundles;
}, },
getUserOffers: async (state, userId) => { getUserOffers: async function getUserOffers(state, userId) {
const { data: offers } = await api.get(`/user/${userId}/offers`); const { data: offers } = await api.get(`/user/${userId}/offers`);
return offers; return offers;
}, },
getOffers: async (userId) => { getOffers: async function getOffers(userId) {
const { data } = await api.get(`/user/${userId}/offers`); const { data } = await api.get(`/user/${userId}/offers`);
return data; return data;
}, },
getLogin: async () => { getLogin: async function getLogin() {
try { try {
const { data: user } = await api.get('/auth/login') const { data: user } = await api.get('/auth/login')
console.log(user); console.log(user);
@@ -31,16 +31,17 @@ const actions = {
} }
}, },
postLogin: async (email, password) => { postLogin: async function postLogin(email, password) {
const { data } = await api.post('/auth/login', { const { data } = await api.post('/auth/login', {
email, email,
password, password,
// discogs,
}); });
return data; return data;
}, },
postRegister: async (email, password) => { postRegister: async function postRegister(email, password) {
const { data } = await api.post('/auth/register', { const { data } = await api.post('/auth/register', {
email, email,
password, password,
+5 -4
View File
@@ -9,7 +9,6 @@ import Header from './components/header';
import Home from './routes/home'; import Home from './routes/home';
import Login from './routes/login'; import Login from './routes/login';
import Register from './routes/register';
import Search from './routes/search'; import Search from './routes/search';
import User from './routes/user'; import User from './routes/user';
@@ -22,6 +21,8 @@ function App() {
async function getLogin() { async function getLogin() {
const user = await actions.getLogin(); const user = await actions.getLogin();
if (!user) return route('/login'); if (!user) return route('/login');
setUser(user);
route('/');
} }
getLogin(); getLogin();
@@ -32,15 +33,15 @@ function App() {
} }
return ( return (
<div id="app"> <main id="app">
<Header /> <Header user={user} />
<Router> <Router>
<Route path="/" component={Home} /> <Route path="/" component={Home} />
<Route path="/login" component={Login} success={loginSuccess} /> <Route path="/login" component={Login} success={loginSuccess} />
<Route path="/search" component={Search} /> <Route path="/search" component={Search} />
<Route path="/user/:userId" component={User} /> <Route path="/user/:userId" component={User} />
</Router> </Router>
</div> </main>
); );
} }
+14 -10
View File
@@ -1,17 +1,21 @@
import { h } from 'preact'; import { h } from 'preact';
import { Link } from 'preact-router/match'; import { Link } from 'preact-router/match';
const Header = () => ( const Header = ({ user }) => {
<header class={'header'}> const nav = () => (
<h1>Mates Crates</h1>
<nav> <nav>
<Link activeClassName={'active'} href="/">Home</Link> <a href="/search">Offers</a>
<Link activeClassName={'active'} href="/user/cf2384d7-f67f-45a3-a522-68e847345073">ntr</Link> <a href="/search">Search</a>
<Link activeClassName={'active'} href="/user/0ad6c6fc-4637-4ea6-9ecc-d0dcdc7f2da2">jen</Link> <a href={`/user/${user.id}`}>User</a>
<Link activeClassName={'active'} href="/user/ac132ae3-7876-4877-ace4-c0d96a2e4174">james</Link>
<Link activeClassName={'active'} href="/user/aa2b287d-ea3f-443a-8559-e1477b8fec07">rhys</Link>
</nav> </nav>
</header> );
);
return (
<header class={'header'}>
<h1>N8CR8</h1>
{user ? nav() : null}
</header>
);
}
export default Header; export default Header;
+56 -9
View File
@@ -6,6 +6,8 @@ const Login = ({ success }) => {
const [mode, setMode] = useState('login') const [mode, setMode] = useState('login')
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
// const [discogs, setDiscogs] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
async function postLogin() { async function postLogin() {
const user = await actions.postLogin(email, password); const user = await actions.postLogin(email, password);
@@ -17,17 +19,62 @@ const Login = ({ success }) => {
postLogin(); postLogin();
} }
return ( if (mode === 'register') return (
<div class={'home'}> <div class="login">
<form action="" method="get" > <form action="">
<label for="email">email: </label> <div class="item">
<input type="email" name="email" id="email" value={email} required onChange={(e) => setEmail(e.target.value)} /> <label for="email">email: </label>
<label for="password">password: </label> <input type="email" name="email" id="email" value={email} required onChange={(e) => setEmail(e.target.value)} />
<input type="password" name="password" id="password" value={password} required onChange={(e) => setPassword(e.target.value)} /> </div>
<input type="submit" value="Login" onClick={handleSubmit} /> <div class="item">
<label for="password">confirm password: </label>
<input type="password" name="password" id="password" value={password} required onChange={(e) => setConfirmPassword(e.target.value)} />
</div>
<div class="item">
<label for="password">confirm password: </label>
<input type="password" name="password" id="password" value={password} required onChange={(e) => setConfirmPassword(e.target.value)} />
</div>
<div class="item">
<input type="submit" value="register" onClick={handleSubmit} />
</div>
</form> </form>
<button onClick={() => setMode('login')} >Click to login</button>
</div>
);
return (
<div class="login">
<form action="">
<div class="item">
<label for="email">email: </label>
<input type="email" name="email" id="email" value={email} required onChange={(e) => setEmail(e.target.value)} />
</div>
<div class="item">
<label for="password">password: </label>
<input type="password" name="password" id="password" value={password} required onChange={(e) => setPassword(e.target.value)} />
</div>
<div class="item">
<input type="submit" value="login" onClick={handleSubmit} />
</div>
</form>
<button onClick={() => setMode('register')} >Click to register</button>
</div> </div>
); );
}; };
export default Login; function LoginPage({ success }) {
return (
<div class="landing">
<Login success={success} />
<div class="info">
<p>Mates Crates is a record importing service.</p>
<p>We bundle orders together from thousands of online stores to reduce the cost of shipping vinyl.</p>
<p>Based in Preston.</p>
</div>
</div>
)
}
export default LoginPage;
-41
View File
@@ -1,41 +0,0 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import actions from './../actions';
const Register = ({ success }) => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('');
async function postRegister() {
const user = await actions.postRegister(email, password);
success(user);
}
function handleSubmit(e) {
e.preventDefault();
postRegister();
}
function emailChange(e) {
setEmail(e.target.value);
}
function passwordChange(e) {
setPassword(e.target.value);
}
return (
<div class={'home'}>
<form action="" method="get" >
<label for="email">email: </label>
<input type="email" name="email" id="email" value={email} required onChange={emailChange} />
<label for="password">password: </label>
<input type="password" name="password" id="password" value={password} required onChange={passwordChange} />
<input type="submit" value="Register" onClick={handleSubmit} />
</form>
</div>
);
};
export default Register;
+2 -2
View File
@@ -31,9 +31,9 @@ const User = () => {
<div class={'home'}> <div class={'home'}>
<form action="" method="get" class="form-example"> <form action="" method="get" class="form-example">
<label for="artist">Artist: </label> <label for="artist">Artist: </label>
<input type="text" name="artist" id="artist" value={artist} required /> <input type="text" name="artist" id="artist" value={artist} onChange={e => setArtist(e.target.value) } required />
<label for="title">Title: </label> <label for="title">Title: </label>
<input type="text" name="title" id="title" value={title} required /> <input type="text" name="title" id="title" value={title} onChange={e => setTitle(e.target.value)} required />
<input type="submit" value="Search" onClick={handleSubmit} /> <input type="submit" value="Search" onClick={handleSubmit} />
</form> </form>
<div> <div>
+173 -81
View File
@@ -1,91 +1,183 @@
html, body { // Digital Ocean reset
height: 100%; html {
width: 100%; box-sizing: border-box;
padding: 0; font-size: 16px;
margin: 0;
background: #FAFAFA;
font-family: 'Helvetica Neue', arial, sans-serif;
font-weight: 400;
color: #444;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
* { *, *:before, *:after {
box-sizing: border-box; box-sizing: inherit;
} }
#app { body, h1, h2, h3, h4, h5, h6, p, ol, ul {
height: 100%; margin: 0;
padding: 0;
font-weight: normal;
}
ol, ul {
list-style: none;
}
img {
max-width: 100%;
height: auto;
}
/* latin */
@font-face {
font-family: 'Raleway';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/raleway/v28/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrE.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
// stuff from UX laws
html {
color:var(--base-font-color);
font-size:clamp(var(--min-font-size),calc(50% + 1vw),var(--max-font-size));
font-family:var(--base-font-family);
line-height:var(--base-line-height);
-webkit-text-size-adjust:100%;
background:var(--base-background-color);
box-sizing:border-box
}
// @charset "UTF-8";
:root {
--breakpoint-medium: 35em;
--breakpoint-large: 56em;
--color-dark-blue: #101014;
--color-mid-blue: #16171d;
--color-light-blue: #1f2128;
--color-mid-grey: #9BA0B3;
--color-off-white: #F4F1D0;
--color-accent-blue: #5E9ED6;
--base-accent-color: var(--color-accent-blue);
--hover-link-color: var(--color-off-white);
--base-background-color: var(--color-dark-blue);
--secondary-background-color: var(--color-mid-blue);
--tertiary-background-color: var(--color-light-blue);
--small-font-size: 0.75em;
--base-font-size: 1em;
--smedium-font-size: 1.333em;
--medium-font-size: 1.777em;
--large-font-size: 2.369em;
--xlarge-font-size: 3.157em;
--base-font-family: 'Raleway', Arial, Sans-Serif;
--secondary-font-family: monospace;
--base-font-color: var(--color-off-white);
--secondary-font-color: var(--color-mid-grey);
--base-line-height: 1.4;
--header-line-height: 1.2;
--min-font-size: var(--base-font-size);
--max-font-size: var(--large-font-size);
--base-duration: .3s;
--base-timing: cubic-bezier(0.645, 0.045, 0.355, 1.000);
--base-border-color: var(--secondary-font-color);
--base-border: 1px solid var(--base-border-color);
--hover-border: 1px solid var(--base-font-color);
--focus-border: 1px solid var(--base-font-color);
--base-border-radius: .2rem;
--base-focus: 2px solid var(--base-accent-color);
--base-outline-offset: 0.25em;
--base-box-shadow: 0 1px 2px 0px rgba(0, 0, 0, .25);
--hover-box-shadow: 0 2px 10px 1px rgba(0, 0, 0, .5);
--base-spacing: 1rem;
--base-spacing-half: calc(var(--base-spacing)/2);
--utility-bar-header-height: calc(var(--base-spacing) * 1.5);
}
button, input, optgroup, select, textarea {
margin:0;
border:var(--base-border);
border-radius:var(--base-border-radius);
background:0 0;
font-family:inherit;
font-size:100%;
color:inherit;
line-height:var(--base-line-height);
}
input:hover, textarea:hover {
color:var(--base-font-color);
border:var(--hover-border)
}
input:focus, textarea:focus {
border:var(--focus-border);
outline:0
}
button, input {
overflow:visible
}
button, select {
text-transform:none
}
button, [type=button], [type=reset], [type=submit] {
-webkit-appearance:button;
cursor:pointer
}
// ok actual styles
body {
padding-top: calc(var(--base-spacing)/2);
}
main {
padding: 0 10% 0 10%;
}
button, [type=submit] {
padding: var(--base-spacing-half);
}
.landing {
display: flex;
.login {
flex: 1;
}
.info {
flex: 1;
p {
margin-bottom: var(--base-spacing);
}
}
}
form {
margin-bottom: var(--base-spacing);
label {
margin-bottom: var(--base-spacing-half);
}
label, input {
display: block;
}
}
.header {
display: flex;
} }
.home { .home {
padding: 56px 20px; min-height: 100%;
min-height: 100%; width: 100%;
width: 100%;
display: flex; display: flex;
.bundle { .bundle {
flex: 1; flex: 1;
padding-right: 1em; padding-right: 1em;
} }
} }
.profile {
padding: 56px 20px;
min-height: 100%;
width: 100%;
}
.header {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 56px;
padding: 0;
background: #673AB7;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
z-index: 50;
}
.header {
h1 {
float: left;
margin: 0;
padding: 0 15px;
font-size: 24px;
line-height: 56px;
font-weight: 400;
color: #FFF;
}
nav {
float: right;
font-size: 100%;
}
nav a {
display: inline-block;
height: 56px;
line-height: 56px;
padding: 0 15px;
min-width: 50px;
text-align: center;
background: rgba(255,255,255,0);
text-decoration: none;
color: #FFF;
will-change: background-color;
}
nav a:hover,
nav a:active {
background: rgba(0,0,0,0.2);
}
nav a.active {
background: rgba(0,0,0,0.4);
}
}