css and some nav
This commit is contained in:
1 parent
a8e5e5d7cd
commit
9a42c290ae
10 files changed
+262
-184
No files matched your search
+12
-12
@@ -22,17 +22,17 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
const app = new koa();
|
||||
const server = http.createServer(app.callback());
|
||||
|
||||
// app.use(async (ctx, next) => {
|
||||
// try {
|
||||
// await next();
|
||||
// } catch (err) {
|
||||
// log.error(err);
|
||||
// ctx.status = err.statusCode || err.status || 500;
|
||||
// ctx.body = {
|
||||
// message: err.message
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
app.use(async (ctx, next) => {
|
||||
try {
|
||||
await next();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
ctx.status = err.statusCode || err.status || 500;
|
||||
ctx.body = {
|
||||
error: err.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const router = koaRouter();
|
||||
|
||||
@@ -128,7 +128,7 @@ export default function createApi(log: Log, pg: Pg, discogs) {
|
||||
const { id } = ctx.request.params;
|
||||
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())
|
||||
.map(b => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export default function authRouter(log, pg: Pg) {
|
||||
|
||||
authRouter.post('/login', async (ctx, next) => {
|
||||
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.status = 200;
|
||||
@@ -50,11 +50,12 @@ export default function authRouter(log, pg: Pg) {
|
||||
});
|
||||
|
||||
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;
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
discogs: user.discogs,
|
||||
};
|
||||
@@ -68,6 +69,7 @@ export default function authRouter(log, pg: Pg) {
|
||||
ctx.login(user);
|
||||
ctx.status = 200;
|
||||
ctx.body = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
discogs: user.discogs,
|
||||
};
|
||||
|
||||
@@ -6,22 +6,22 @@ const api = axios.create({
|
||||
});
|
||||
|
||||
const actions = {
|
||||
getBundles: async () => {
|
||||
getBundles: async function getBundles() {
|
||||
const { data: bundles } = await api.get('/bundles');
|
||||
return bundles;
|
||||
},
|
||||
|
||||
getUserOffers: async (state, userId) => {
|
||||
getUserOffers: async function getUserOffers(state, userId) {
|
||||
const { data: offers } = await api.get(`/user/${userId}/offers`);
|
||||
return offers;
|
||||
},
|
||||
|
||||
getOffers: async (userId) => {
|
||||
getOffers: async function getOffers(userId) {
|
||||
const { data } = await api.get(`/user/${userId}/offers`);
|
||||
return data;
|
||||
},
|
||||
|
||||
getLogin: async () => {
|
||||
getLogin: async function getLogin() {
|
||||
try {
|
||||
const { data: user } = await api.get('/auth/login')
|
||||
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', {
|
||||
email,
|
||||
password,
|
||||
// discogs,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
postRegister: async (email, password) => {
|
||||
postRegister: async function postRegister(email, password) {
|
||||
const { data } = await api.post('/auth/register', {
|
||||
email,
|
||||
password,
|
||||
|
||||
+5
-4
@@ -9,7 +9,6 @@ import Header from './components/header';
|
||||
|
||||
import Home from './routes/home';
|
||||
import Login from './routes/login';
|
||||
import Register from './routes/register';
|
||||
import Search from './routes/search';
|
||||
import User from './routes/user';
|
||||
|
||||
@@ -22,6 +21,8 @@ function App() {
|
||||
async function getLogin() {
|
||||
const user = await actions.getLogin();
|
||||
if (!user) return route('/login');
|
||||
setUser(user);
|
||||
route('/');
|
||||
}
|
||||
|
||||
getLogin();
|
||||
@@ -32,15 +33,15 @@ function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="app">
|
||||
<Header />
|
||||
<main id="app">
|
||||
<Header user={user} />
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/login" component={Login} success={loginSuccess} />
|
||||
<Route path="/search" component={Search} />
|
||||
<Route path="/user/:userId" component={User} />
|
||||
</Router>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { h } from 'preact';
|
||||
import { Link } from 'preact-router/match';
|
||||
|
||||
const Header = () => (
|
||||
<header class={'header'}>
|
||||
<h1>Mates Crates</h1>
|
||||
const Header = ({ user }) => {
|
||||
const nav = () => (
|
||||
<nav>
|
||||
<Link activeClassName={'active'} href="/">Home</Link>
|
||||
<Link activeClassName={'active'} href="/user/cf2384d7-f67f-45a3-a522-68e847345073">ntr</Link>
|
||||
<Link activeClassName={'active'} href="/user/0ad6c6fc-4637-4ea6-9ecc-d0dcdc7f2da2">jen</Link>
|
||||
<Link activeClassName={'active'} href="/user/ac132ae3-7876-4877-ace4-c0d96a2e4174">james</Link>
|
||||
<Link activeClassName={'active'} href="/user/aa2b287d-ea3f-443a-8559-e1477b8fec07">rhys</Link>
|
||||
<a href="/search">Offers</a>
|
||||
<a href="/search">Search</a>
|
||||
<a href={`/user/${user.id}`}>User</a>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
|
||||
return (
|
||||
<header class={'header'}>
|
||||
<h1>N8CR8</h1>
|
||||
{user ? nav() : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -6,6 +6,8 @@ const Login = ({ success }) => {
|
||||
const [mode, setMode] = useState('login')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('');
|
||||
// const [discogs, setDiscogs] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
|
||||
async function postLogin() {
|
||||
const user = await actions.postLogin(email, password);
|
||||
@@ -17,17 +19,62 @@ const Login = ({ success }) => {
|
||||
postLogin();
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={'home'}>
|
||||
<form action="" method="get" >
|
||||
if (mode === 'register') 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">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>
|
||||
<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)} />
|
||||
<input type="submit" value="Login" onClick={handleSubmit} />
|
||||
</div>
|
||||
<div class="item">
|
||||
<input type="submit" value="login" onClick={handleSubmit} />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button onClick={() => setMode('register')} >Click to register</button>
|
||||
</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;
|
||||
@@ -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;
|
||||
@@ -31,9 +31,9 @@ const User = () => {
|
||||
<div class={'home'}>
|
||||
<form action="" method="get" class="form-example">
|
||||
<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>
|
||||
<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} />
|
||||
</form>
|
||||
<div>
|
||||
|
||||
+167
-75
@@ -1,26 +1,176 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
* {
|
||||
// Digital Ocean reset
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p, ol, ul {
|
||||
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 {
|
||||
padding: 56px 20px;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -31,61 +181,3 @@ html, body {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user