want events

This commit is contained in:
ntr committed 2022-08-17 23:22:48 +10:00
1 parent f5e75f4fe6
commit 30bf7426e4
19 files changed
+281 -96

No files matched your search

+1 -1
View File
@@ -40,7 +40,7 @@ model User {
model Want { model Want {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
release Int? release Int
data Json data Json
userId String @db.Uuid userId String @db.Uuid
-1
View File
@@ -11,7 +11,6 @@ const services = createServices(log);
const HOST = 'http://localhost:41337'; const HOST = 'http://localhost:41337';
before(function setup() { before(function setup() {
console.log('setup')
services.api.listen(); services.api.listen();
}); });
+29 -11
View File
@@ -14,8 +14,9 @@ import type { Listing, User, Want, Prisma } from '@prisma/client';
import { Pg } from './pg'; import { Pg } from './pg';
import routerAuth from './routers/router.auth'; import routerAuth from './routers/router.auth';
import { Events } from './events';
export default function createApi(log: Log, pg: Pg, discogs) { export default function createApi(log: Log, pg: Pg, discogs, events) {
const PORT = 41337; const PORT = 41337;
const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth'; const SECRET = 'ohja4Eif9eidah3aj5ta2haa4iemooth';
@@ -147,23 +148,40 @@ export default function createApi(log: Log, pg: Pg, discogs) {
ctx.response.body = listings; ctx.response.body = listings;
}); });
router.get('/wants', async function wantPost(ctx, next) { router.post('/want', async function wantPost(ctx, next) {
const { userId, releaseId } = ctx.request.body; ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
const user = await pg.userFindId(userId); const { releaseId } = ctx.request.body;
const user = await pg.userFindId(ctx.state.user.id);
const release = await discogs.release(releaseId); const release = await discogs.release(releaseId);
const want = await pg.wantUpsert(release, user); const want = await pg.wantCreate([release], user);
ctx.response.body = want;
// fetch listings
const listings = await discogs.listings(want);
ctx.response.body = { want, listings };
}); });
router.get('/wants', async function wantGet(ctx, next) { router.post('/want/discogs', async function wantListPost(ctx, next) {
ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
log.warn(ctx.state.user); const { username } = ctx.request.body;
const user = ctx.state.user; const user = await pg.userFindId(ctx.state.user.id);
const wants = await pg.wantFind(user);
const wants = await discogs.wantListSync(username, user);
events.emit(Events.WantsAdded, wants);
ctx.response.body = wants; ctx.response.body = wants;
}); });
router.post('/wants/search', async function wantPost(ctx, next) { router.get('/want/all', async function wantGet(ctx, next) {
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
const user = ctx.state.user;
const wants = await pg.wantList(user);
ctx.response.body = wants;
});
router.post('/want/search', async function wantPost(ctx, next) {
const { artist, title } = ctx.request.body; const { artist, title } = ctx.request.body;
const results = await discogs.search(artist, title); const results = await discogs.search(artist, title);
ctx.response.body = results; ctx.response.body = results;
+10 -26
View File
@@ -31,30 +31,15 @@ export default function discogs(log: Log, pg: Pg) {
return null; return null;
} }
// async function user(discogs: string) { async function wantListSync(username: string, user: User): Promise<Want[]> {
// const user = await pg.userFindName(discogs);
// if (user) return user;
// log.debug('fetching user', discogs);
// const { data } = await api.get(`${apiBase}/users/${discogs}`);
// return pg.userUpsert(data.username, data.username, data);
// }
async function wantlist(user: User): Promise<Want[]> {
const stored = await pg.userWants(user);
if (stored.length) return stored;
if (!user.discogs) {
log.error(user, 'attempted to fetch wantlist of non discogs user');
throw new Error('User is not associated with a discogs account');
}
return wantlistLatest(user);
}
async function wantlistLatest(user: User): Promise<Want[]> {
const { username } = user.data as { username: string };
const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`); const { data: { wants, pagination }} = await api.get(`${apiBase}/users/${username}/wants`);
return Bluebird.mapSeries(wants, async r => pg.wantUpsert(r, user)); const releases = await Promise.all(wants.map(want => release(want.id)));
// convert want object to actual release and ignore duplicate errors
const results = await Promise.allSettled(releases.map(release => pg.wantCreate(release, user)));
// @ts-ignore
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
return additions;
} }
async function release(id: DiscogsRelease): Promise<any> { async function release(id: DiscogsRelease): Promise<any> {
@@ -99,6 +84,7 @@ export default function discogs(log: Log, pg: Pg) {
try { try {
log.debug('fetching listing', id); log.debug('fetching listing', id);
const res = await api.get(`${apiBase}/marketplace/listings/${id}`); const res = await api.get(`${apiBase}/marketplace/listings/${id}`);
log.info(res.data);
return pg.listingUpsert(res.data, want); return pg.listingUpsert(res.data, want);
} catch (e) { } catch (e) {
return handleApiError(e); return handleApiError(e);
@@ -106,9 +92,7 @@ export default function discogs(log: Log, pg: Pg) {
} }
return { return {
// user, wantListSync,
wantlist,
wantlistLatest,
inventory, inventory,
listings, listings,
listing, listing,
+25
View File
@@ -0,0 +1,25 @@
import { EventEmitter } from 'node:events';
import type Log from 'bunyan';
import type { Listing, User, Want, Prisma } from '@prisma/client';
import { Pg } from './pg';
import routerAuth from './routers/router.auth';
export enum Events {
WantsAdded = 'WantsAdded',
}
export function eventService(log: Log, pg: Pg, discogs) {
const events = new EventEmitter({ captureRejections: true });
events.on('error', function eventError(e) {
log.error(e);
});
events.on(Events.WantsAdded, async function wantsAdded(wants: Want[]) {
log.info('wants added', wants);
});
return events;
}
+10 -16
View File
@@ -16,8 +16,8 @@ export interface Pg {
userCreate: (params: UserCreateParams) => Promise<User>, userCreate: (params: UserCreateParams) => Promise<User>,
userWants: (user: User) => Promise<Want[]>, userWants: (user: User) => Promise<Want[]>,
wantUpsert: (want: Want, user: User) => Promise<Want>, wantCreate: (discogsWant: any, user: User) => Promise<Want>,
wantFind: (user: User) => Promise<Want[]>, wantList: (user: User) => Promise<Want[]>,
listingUpsert: (listing: Listing, want: Want) => Promise<Listing>, listingUpsert: (listing: Listing, want: Want) => Promise<Listing>,
listingFind: (id: DiscogsListing) => Promise<Listing>, listingFind: (id: DiscogsListing) => Promise<Listing>,
listingRmUnlisted: (want: Want, ids: DiscogsListing[]) => Promise<number>, listingRmUnlisted: (want: Want, ids: DiscogsListing[]) => Promise<number>,
@@ -60,22 +60,15 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
}); });
} }
async function wantUpsert(dcWant, user) { async function wantCreate(discogsRelease, user) {
const values: { userId: UUID, release: DiscogsListing, data: any } = { const data: { userId: UUID, release: DiscogsListing, data: any } = {
data: dcWant, data: discogsRelease,
release: dcWant.id, release: discogsRelease.id,
userId: user.id, userId: user.id,
}; };
return prisma.want.upsert({ return prisma.want.create({
// get these from the generated types data,
// ./node_modules/.prisma/client/index.d.ts
where: { userId_release: {
userId: values.userId,
release: values.release || null,
}},
update: values,
create: values,
}); });
} }
@@ -154,7 +147,8 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
userCreate, userCreate,
userWants, userWants,
wantUpsert, wantCreate,
wantList,
listingUpsert, listingUpsert,
listingFind, listingFind,
listingRmUnlisted, listingRmUnlisted,
+7 -5
View File
@@ -2,20 +2,22 @@ import { PrismaClient } from '@prisma/client'
import discogsService from './discogs'; import discogsService from './discogs';
import pgService from './pg'; import pgService from './pg';
import { logAxiosError } from './utils';
import createLog from './log'; import createLog from './log';
import createApi from './api'; import createApi from './api';
import { eventService } from './events';
export default function createServices(log) { export default function createServices(log) {
const prisma = new PrismaClient(); const prisma = new PrismaClient();
const pg = pgService(log, prisma); const pg = pgService(log, prisma);
const discogs = discogsService(log, pg); const discogs = discogsService(log, pg);
const api = createApi(log, pg, discogs); const events = eventService(log, pg, discogs);
const api = createApi(log, pg, discogs, events);
return { return {
prisma,
pg,
discogs,
api, api,
discogs,
events,
pg,
prisma,
}; };
} }
+1
View File
@@ -0,0 +1 @@
This application uses Discogs’ API but is not affiliated with, sponsored or endorsed by Discogs. ‘Discogs’ is a trademark of Zink Media, LLC.
+23 -9
View File
@@ -12,6 +12,7 @@
"preact": "^10.10.0", "preact": "^10.10.0",
"preact-render-to-string": "^5.2.1", "preact-render-to-string": "^5.2.1",
"preact-router": "^3.2.1", "preact-router": "^3.2.1",
"react-feather": "^2.0.10",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"redux": "^4.2.0", "redux": "^4.2.0",
"unistore": "^3.5.2" "unistore": "^3.5.2"
@@ -12778,7 +12779,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -15798,7 +15798,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@@ -15808,8 +15807,7 @@
"node_modules/prop-types/node_modules/react-is": { "node_modules/prop-types/node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true
}, },
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.7",
@@ -16113,6 +16111,17 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-feather": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": ">=16.8.6"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -31062,8 +31071,7 @@
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
"dev": true
}, },
"object-copy": { "object-copy": {
"version": "0.1.0", "version": "0.1.0",
@@ -33326,7 +33334,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"requires": { "requires": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@@ -33336,8 +33343,7 @@
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true
} }
} }
}, },
@@ -33580,6 +33586,14 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"react-feather": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.10.tgz",
"integrity": "sha512-BLhukwJ+Z92Nmdcs+EMw6dy1Z/VLiJTzEQACDUEnWMClhYnFykJCGWQx+NmwP/qQHGX/5CzQ+TGi8ofg2+HzVQ==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-is": { "react-is": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+1
View File
@@ -22,6 +22,7 @@
"preact": "^10.10.0", "preact": "^10.10.0",
"preact-render-to-string": "^5.2.1", "preact-render-to-string": "^5.2.1",
"preact-router": "^3.2.1", "preact-router": "^3.2.1",
"react-feather": "^2.0.10",
"react-redux": "^8.0.2", "react-redux": "^8.0.2",
"redux": "^4.2.0", "redux": "^4.2.0",
"unistore": "^3.5.2" "unistore": "^3.5.2"
+9 -1
View File
@@ -49,7 +49,15 @@ const actions = {
}, },
getWants: async function getWants() { getWants: async function getWants() {
return api.get('/wants'); return api.get('/want/all');
},
postWant: async function postWant(releaseId) {
return api.post('/want', { releaseId });
},
postDiscogs: async function postDiscogs(username) {
return api.post('/want/discogs', { username });
}, },
getLogin: async function getLogin() { getLogin: async function getLogin() {
+4 -2
View File
@@ -9,7 +9,8 @@ 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 Search from './routes/search'; import Search from './routes/search';
import User from './routes/user'; import List from './routes/list';
import Offers from './routes/offers';
function App() { function App() {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
@@ -38,7 +39,8 @@ function App() {
<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="/list/" component={User} user={user} /> <Route path="/list" component={List} user={user} />
<Route path="/offers" component={Offers} user={user} />
</Router> </Router>
</main> </main>
); );
+1 -1
View File
@@ -4,7 +4,7 @@ import { Link } from 'preact-router/match';
const Header = ({ user }) => { const Header = ({ user }) => {
const nav = () => ( const nav = () => (
<nav> <nav>
<a href="/search">Offers</a> <a href="/">Offers</a>
<a href='/list'>List</a> <a href='/list'>List</a>
<a href="/search">Search</a> <a href="/search">Search</a>
</nav> </nav>
+20
View File
@@ -0,0 +1,20 @@
import { h } from 'preact';
function Want(want) {
console.log(want);
const r = want.data;
return (
<article key={r.id}>
<div>
<h3>{r.artists_sort} - {r.title}</h3>
<h5 class='alt-text' >{r.year}</h5>
<h5 class='alt-text'>{r.country}</h5>
<h5 class='alt-text'>{r.formats[0].descriptions.join(' ')}</h5>
</div>
<img src={r.thumb} />
</article>
);
}
export default Want;
+7 -2
View File
@@ -4,12 +4,17 @@ import { useState, useEffect } from 'preact/hooks';
import actions from './../actions'; import actions from './../actions';
import Bundle from './../components/bundle'; import Bundle from './../components/bundle';
function Home() { function Home() {
const [bundles, setBundles] = useState([]); const [bundles, setBundles] = useState([]);
useEffect(() => { useEffect(() => {
actions.getBundles().then(setBundles); async function getBundles() {
const [error, bundles] = await actions.getBundles();
if (error) return console.error(error);
console.log(bundles, 'bundles')
setBundles(bundles);
}
getBundles();
}, []); }, []);
if (!bundles.length) { if (!bundles.length) {
@@ -3,40 +3,37 @@ import { useState, useEffect } from 'preact/hooks';
// import style from './style.less'; // import style from './style.less';
import actions from './../actions'; import actions from './../actions';
import Bundle from './../components/bundle'; import Want from './../components/want';
interface Props { interface Props {
user: any, user: any,
offers: any[], wants: any[],
getUserOffers: (userId: string) => void, getUserOffers: (userId: string) => void,
} }
const User = ({ user }: Props) => { const List = ({ user }: Props) => {
const [offers, setOffers] = useState([]); const [wants, setOffers] = useState([]);
useEffect(() => { useEffect(() => {
async function getWants() { async function getWants() {
const [error, wants] = await actions.getWants(); const [error, wants] = await actions.getWants();
if (error) return console.error(error); if (error) return console.error(error);
console.log(wants, 'wants')
setOffers(wants); setOffers(wants);
} }
getWants(); getWants();
}, []); }, []);
if (!offers.length) { if (!wants.length) {
return ( return <div> loading... </div>;
<div class={'home'}>
loading...
</div>
);
} }
return ( return (
<div class={'home'}> <div class="list">
{offers.map(Bundle)} {wants.map(Want)}
</div> </div>
); );
}; };
export default User; export default List;
+70
View File
@@ -0,0 +1,70 @@
import { h } from 'preact';
import { useState } from 'preact/hooks';
import axios from 'axios';
import actions from '../actions';
function result(r) {
function onClick() {
actions.postWant(r.id);
}
return (
<article key={r.id}>
<button onClick={onClick}> + </button>
<div>
<h3>{r.title}</h3>
<h5 class='alt-text' >{r.year}</h5>
<h5 class='alt-text'>{r.country}</h5>
<h5 class='alt-text'>{r.format.join(' ')}</h5>
</div>
<img src={r.thumb} />
</article>
);
}
const User = () => {
const [results, setResults] = useState([]);
const [noResults, setNoResults] = useState(false);
async function fetchResults() {
const { data } = await actions.getBundles();
console.log(data);
if (data.length === 0) {
setResults([]);
return setNoResults(true);
}
setNoResults(false);
setResults(data);
}
function handleSubmit(e) {
e.preventDefault();
fetchResults();
}
const topEl = results.length
? <button onClick={() => window.scrollTo(0, 0)}> top </button>
: null;
const noResultsEl = noResults
? <article>No Results</article>
: null;
return (
<div class='search'>
<div></div>
<div></div>
<div>
{noResultsEl}
{results.map(result)}
{topEl}
</div>
</div>
);
};
export default User;
+35 -4
View File
@@ -1,13 +1,20 @@
import { h } from 'preact'; import { h } from 'preact';
import { useState } from 'preact/hooks'; import { useState } from 'preact/hooks';
import axios from 'axios'; import axios from 'axios';
import { ExternalLink } from 'react-feather';
import actions from '../actions';
function result(r) { function result(r) {
function onClick() {
actions.postWant(r.id);
}
return ( return (
<article key={r.id}> <article key={r.id}>
<button> + </button> <button onClick={onClick}> + </button>
<div> <div>
<h3>{r.title}</h3> <h3>{r.title} <a href={`https://discogs.com${r.uri}`}><ExternalLink /></a></h3>
<h5 class='alt-text' >{r.year}</h5> <h5 class='alt-text' >{r.year}</h5>
<h5 class='alt-text'>{r.country}</h5> <h5 class='alt-text'>{r.country}</h5>
<h5 class='alt-text'>{r.format.join(' ')}</h5> <h5 class='alt-text'>{r.format.join(' ')}</h5>
@@ -20,11 +27,12 @@ function result(r) {
const User = () => { const User = () => {
const [artist, setArtist] = useState('nine inch nails') const [artist, setArtist] = useState('nine inch nails')
const [title, setTitle] = useState('the fragile'); const [title, setTitle] = useState('the fragile');
const [username, setUsername] = useState('');
const [results, setResults] = useState([]); const [results, setResults] = useState([]);
const [noResults, setNoResults] = useState(false); const [noResults, setNoResults] = useState(false);
async function fetchResults() { async function fetchResults() {
const { data } = await axios.post(`http://localhost:41337/wants/search`, { const { data } = await axios.post(`http://localhost:41337/want/search`, {
artist, artist,
title, title,
}); });
@@ -40,11 +48,22 @@ const User = () => {
setResults(data); setResults(data);
} }
async function submitWantlist() {
const [error, wants] = await actions.postDiscogs(username);
// setNoResults(false);
// setResults(data);
}
function handleSubmit(e) { function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
fetchResults(); fetchResults();
} }
function handleDiscogsSubmit(e) {
e.preventDefault();
submitWantlist();
}
const topEl = results.length const topEl = results.length
? <button onClick={() => window.scrollTo(0, 0)}> top </button> ? <button onClick={() => window.scrollTo(0, 0)}> top </button>
: null; : null;
@@ -55,6 +74,7 @@ const User = () => {
return ( return (
<div class='search'> <div class='search'>
<div>
<form action="" method="get"> <form action="" method="get">
<label for="artist">Artist</label> <label for="artist">Artist</label>
<input type="text" name="artist" placeholder="Ben Klock" id="artist" value={artist} onChange={e => setArtist(e.target.value) } required /> <input type="text" name="artist" placeholder="Ben Klock" id="artist" value={artist} onChange={e => setArtist(e.target.value) } required />
@@ -62,7 +82,18 @@ const User = () => {
<input type="text" name="title" placeholder="Subzero" id="title" value={title} onChange={e => setTitle(e.target.value)} required /> <input type="text" name="title" placeholder="Subzero" 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> <p>Data provided by Discogs.</p>
<hr />
<div>
<p>Sync Discogs Wantlist</p>
<form action="" method="get">
<label for="Discogs">Discogs Username</label>
<input type="text" name="username" placeholder="username" id="username" value={username} onChange={e => setUsername(e.target.value) } required />
<input type="submit" value="Search" onClick={handleDiscogsSubmit} />
</form>
</div>
</div>
<div />
<div> <div>
{noResultsEl} {noResultsEl}
{results.map(result)} {results.map(result)}
+18 -4
View File
@@ -180,16 +180,21 @@ button, [type=submit] {
text-transform: uppercase; text-transform: uppercase;
} }
article {
background-color: var(--secondary-background-color);
}
.search { .search {
display: grid; display: grid;
grid-template-columns: 1fr 0.5fr 2fr; grid-template-columns: 1fr 0.5fr 2fr;
article { article {
display: grid; display: grid;
grid-template-columns: 1fr 4fr 1fr;
background-color: var(--secondary-background-color);
padding: var(--base-spacing); padding: var(--base-spacing);
margin-bottom: var(--base-spacing); margin-bottom: var(--base-spacing);
grid-template-columns: 1fr 4fr 1fr;
}
button { button {
margin-right: var(--base-spacing); margin-right: var(--base-spacing);
@@ -197,6 +202,15 @@ button, [type=submit] {
border-color: var(--secondary-background-color); border-color: var(--secondary-background-color);
} }
} }
.list {
display: flex;
article {
flex: 1;
padding: var(--base-spacing);
margin: var(--base-spacing-half);
}
} }
form { form {
@@ -246,11 +260,11 @@ header {
} }
.home { .home {
display: grid; display: flex;
grid-template-columns: 1fr 0.5fr 2fr;
.bundle { .bundle {
flex: 1; flex: 1;
padding-right: 1em; padding-right: 1em;
} }
} }