bundle offers grouped
This commit is contained in:
1 parent
7946259550
commit
623d0f3938
12 files changed
+228
-148
No files matched your search
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
status stepper
|
status stepper
|
||||||
|
|
||||||
|
queue up wantlist adds to gracefully handle rate limiting
|
||||||
|
|
||||||
push offers
|
push offers
|
||||||
cover off admin routes and commands
|
cover off admin routes and commands
|
||||||
|
|
||||||
|
|||||||
Generated
+30
-41
@@ -11,12 +11,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@koa/cors": "^3.3.0",
|
"@koa/cors": "^3.3.0",
|
||||||
"@koa/router": "^12.0.0",
|
"@koa/router": "^12.0.0",
|
||||||
"@prisma/client": "^4.1.0",
|
"@prisma/client": "^4.5.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"json-rpc-2.0": "^1.4.1",
|
|
||||||
"koa": "^2.13.4",
|
"koa": "^2.13.4",
|
||||||
"koa-body": "^5.0.0",
|
"koa-body": "^5.0.0",
|
||||||
"koa-passport": "^5.0.0",
|
"koa-passport": "^5.0.0",
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^10.0.0",
|
||||||
"node-dev": "^7.4.3",
|
"node-dev": "^7.4.3",
|
||||||
"prisma": "^4.1.0",
|
"prisma": "^4.5.0",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
}
|
}
|
||||||
@@ -136,12 +135,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/client": {
|
"node_modules/@prisma/client": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.5.0.tgz",
|
||||||
"integrity": "sha512-MvfPGAd42vHTiCYxwS6N+2U3F+ukoJ48D2QRnX1zSPJHBkh1CBtshl75daKzvVfgQwSouzSQeugKDej5di+E/g==",
|
"integrity": "sha512-B2cV0OPI1smhdYUxsJoLYQLoMlLH06MUxgFUWQnHodGMX98VRVXKmQE/9OcrTNkqtke5RC+YU24Szxd04tZA2g==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/engines-version": "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
|
"@prisma/engines-version": "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
@@ -156,16 +155,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines": {
|
"node_modules/@prisma/engines": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.5.0.tgz",
|
||||||
"integrity": "sha512-quqHXD3P83NBLVtRlts4SgKHmqgA8GMiyDTJ7af03Wg0gl6F5t65mBYvIuwmD+52vHm42JtIsp/fAO9YIV0JBA==",
|
"integrity": "sha512-4t9ir2SbQQr/wMCNU4YpHWp5hU14J2m3wHUZnGJPpmBF8YtkisxyVyQsKd1e6FyLTaGq8LOLhm6VLYHKqKNm+g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true
|
"hasInstallScript": true
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines-version": {
|
"node_modules/@prisma/engines-version": {
|
||||||
"version": "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8",
|
"version": "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452.tgz",
|
||||||
"integrity": "sha512-cRRJwpHFGFJZvtHbY3GZjMffNBEjjZk68ztn+S2hDgPCGB4H66IK26roK94GJxBodSehwRJ0wGyebC2GoIH1JQ=="
|
"integrity": "sha512-o7LyVx8PPJBLrEzLl6lpxxk2D5VnlM4Fwmrbq0NoT6pr5aa1OuHD9ZG+WJY6TlR/iD9bhmo2LNcxddCMr5Rv2A=="
|
||||||
},
|
},
|
||||||
"node_modules/@tsconfig/node10": {
|
"node_modules/@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
@@ -1873,11 +1872,6 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"js-yaml": "bin/js-yaml.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/json-rpc-2.0": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-OX1NJhpIfuK4GjDnJ/gKtZy1HOYo0l4eL0a4rb0rNeQheX1xlyQ9+JMmPzs/sFNthpS/TXKPWlGo09X7B5l81A=="
|
|
||||||
},
|
|
||||||
"node_modules/keygrip": {
|
"node_modules/keygrip": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
||||||
@@ -2989,13 +2983,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
||||||
"integrity": "sha512-iwqpAT6In1uvMSwQAM3PqmaBdhh2OaQ/2t+n3RjpW4vAKP3R7E1T34FZUU4zGOWtMWm5dt0sPThQkT/h87r6gw==",
|
"integrity": "sha512-9Aeg4qiKlv9Wsjz4NO8k2CzRzlvS3A4FYVJ5+28sBBZ0eEwbiVOE/Jj7v6rZC1tFW2s4GSICQOAyuOjc6WsNew==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/engines": "4.1.0"
|
"@prisma/engines": "4.5.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"prisma": "build/index.js",
|
"prisma": "build/index.js",
|
||||||
@@ -3790,23 +3784,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@prisma/client": {
|
"@prisma/client": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.5.0.tgz",
|
||||||
"integrity": "sha512-MvfPGAd42vHTiCYxwS6N+2U3F+ukoJ48D2QRnX1zSPJHBkh1CBtshl75daKzvVfgQwSouzSQeugKDej5di+E/g==",
|
"integrity": "sha512-B2cV0OPI1smhdYUxsJoLYQLoMlLH06MUxgFUWQnHodGMX98VRVXKmQE/9OcrTNkqtke5RC+YU24Szxd04tZA2g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@prisma/engines-version": "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8"
|
"@prisma/engines-version": "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@prisma/engines": {
|
"@prisma/engines": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.5.0.tgz",
|
||||||
"integrity": "sha512-quqHXD3P83NBLVtRlts4SgKHmqgA8GMiyDTJ7af03Wg0gl6F5t65mBYvIuwmD+52vHm42JtIsp/fAO9YIV0JBA==",
|
"integrity": "sha512-4t9ir2SbQQr/wMCNU4YpHWp5hU14J2m3wHUZnGJPpmBF8YtkisxyVyQsKd1e6FyLTaGq8LOLhm6VLYHKqKNm+g==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
"@prisma/engines-version": {
|
"@prisma/engines-version": {
|
||||||
"version": "4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8",
|
"version": "4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.1.0-48.8d8414deb360336e4698a65aa45a1fbaf1ce13d8.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.5.0-43.0362da9eebca54d94c8ef5edd3b2e90af99ba452.tgz",
|
||||||
"integrity": "sha512-cRRJwpHFGFJZvtHbY3GZjMffNBEjjZk68ztn+S2hDgPCGB4H66IK26roK94GJxBodSehwRJ0wGyebC2GoIH1JQ=="
|
"integrity": "sha512-o7LyVx8PPJBLrEzLl6lpxxk2D5VnlM4Fwmrbq0NoT6pr5aa1OuHD9ZG+WJY6TlR/iD9bhmo2LNcxddCMr5Rv2A=="
|
||||||
},
|
},
|
||||||
"@tsconfig/node10": {
|
"@tsconfig/node10": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
@@ -5100,11 +5094,6 @@
|
|||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"json-rpc-2.0": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-OX1NJhpIfuK4GjDnJ/gKtZy1HOYo0l4eL0a4rb0rNeQheX1xlyQ9+JMmPzs/sFNthpS/TXKPWlGo09X7B5l81A=="
|
|
||||||
},
|
|
||||||
"keygrip": {
|
"keygrip": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
||||||
@@ -5939,12 +5928,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"version": "4.1.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
||||||
"integrity": "sha512-iwqpAT6In1uvMSwQAM3PqmaBdhh2OaQ/2t+n3RjpW4vAKP3R7E1T34FZUU4zGOWtMWm5dt0sPThQkT/h87r6gw==",
|
"integrity": "sha512-9Aeg4qiKlv9Wsjz4NO8k2CzRzlvS3A4FYVJ5+28sBBZ0eEwbiVOE/Jj7v6rZC1tFW2s4GSICQOAyuOjc6WsNew==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@prisma/engines": "4.1.0"
|
"@prisma/engines": "4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
|
|||||||
+2
-2
@@ -21,14 +21,14 @@
|
|||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^10.0.0",
|
||||||
"node-dev": "^7.4.3",
|
"node-dev": "^7.4.3",
|
||||||
"prisma": "^4.1.0",
|
"prisma": "^4.5.0",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^4.7.4"
|
"typescript": "^4.7.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@koa/cors": "^3.3.0",
|
"@koa/cors": "^3.3.0",
|
||||||
"@koa/router": "^12.0.0",
|
"@koa/router": "^12.0.0",
|
||||||
"@prisma/client": "^4.1.0",
|
"@prisma/client": "^4.5.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"bunyan": "^1.8.15",
|
"bunyan": "^1.8.15",
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ model Offer {
|
|||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
status String
|
status String
|
||||||
|
condition String @default("")
|
||||||
|
|
||||||
item Decimal @db.Money
|
item Decimal @db.Money
|
||||||
// markup Decimal @db.Money
|
// markup Decimal @db.Money
|
||||||
|
|||||||
+1
-1
@@ -41,6 +41,7 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
|||||||
for (const want of wants) {
|
for (const want of wants) {
|
||||||
const listings = await populateListings(want);
|
const listings = await populateListings(want);
|
||||||
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings added');
|
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings added');
|
||||||
|
events.emit(Events.ListingsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info({ wants: wants.length }, 'finished syncing');
|
log.info({ wants: wants.length }, 'finished syncing');
|
||||||
@@ -50,7 +51,6 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
|||||||
log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'starting listings population');
|
log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'starting listings population');
|
||||||
await updateWantListings(wants);
|
await updateWantListings(wants);
|
||||||
log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'finished populating listings');
|
log.info({ event: Events.WantsAdded, wants: wants.map(w => wantDetails(w)) }, 'finished populating listings');
|
||||||
events.emit(Events.ListingsUpdated);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
events.on(Events.StartupSync, async function startupSync() {
|
events.on(Events.StartupSync, async function startupSync() {
|
||||||
|
|||||||
+9
-1
@@ -27,7 +27,7 @@ export default function createOffers(log: Log, pg: Pg) {
|
|||||||
|
|
||||||
// log.info({ listings }, 'creating offers for listings')
|
// log.info({ listings }, 'creating offers for listings')
|
||||||
|
|
||||||
const shipping = 10 + Math.random(); // yolt
|
const shipping = 7.5; // yolt
|
||||||
|
|
||||||
// const shipping = bulkShipping / listings.length;
|
// const shipping = bulkShipping / listings.length;
|
||||||
// how to deal with users who don't want all items in a bundle...
|
// how to deal with users who don't want all items in a bundle...
|
||||||
@@ -41,6 +41,8 @@ export default function createOffers(log: Log, pg: Pg) {
|
|||||||
|
|
||||||
status: Status.Pending,
|
status: Status.Pending,
|
||||||
|
|
||||||
|
condition: l.data.condition,
|
||||||
|
|
||||||
item: l.data.price.value,
|
item: l.data.price.value,
|
||||||
shipping,
|
shipping,
|
||||||
total: l.data.price.value + shipping,
|
total: l.data.price.value + shipping,
|
||||||
@@ -145,11 +147,17 @@ export default function createOffers(log: Log, pg: Pg) {
|
|||||||
return sellerOffers;
|
return sellerOffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function userOffers(user: User) {
|
||||||
|
const offers = await pg.userOffers(user);
|
||||||
|
return groupBy(offers, (o) => o.wantId);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generate,
|
generate,
|
||||||
accept,
|
accept,
|
||||||
bundles,
|
bundles,
|
||||||
statusSet,
|
statusSet,
|
||||||
acpOffers,
|
acpOffers,
|
||||||
|
userOffers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+3
-3
@@ -150,7 +150,7 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
return prisma.$transaction(async (prisma) => {
|
return prisma.$transaction(async (prisma) => {
|
||||||
log.debug({ currentListingIds });
|
log.debug({ currentListingIds });
|
||||||
|
|
||||||
const openOfferStatuses = [Status.Pending, Status.Accepted];
|
const protectedOfferStatuses = [Status.Confirmed, Status.Complete];
|
||||||
|
|
||||||
// can't delete a listing with an open offer
|
// can't delete a listing with an open offer
|
||||||
// so delete those offers first
|
// so delete those offers first
|
||||||
@@ -161,7 +161,7 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
notIn: currentListingIds,
|
notIn: currentListingIds,
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
in: openOfferStatuses,
|
notIn: protectedOfferStatuses,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -174,7 +174,7 @@ export default function createPg(log: Log, prisma: PrismaClient): Pg {
|
|||||||
where: {
|
where: {
|
||||||
wantId: want.id,
|
wantId: want.id,
|
||||||
status: {
|
status: {
|
||||||
notIn: openOfferStatuses,
|
in: protectedOfferStatuses,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
|
|||||||
+2
-2
@@ -24,7 +24,7 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function userOffers(): Promise<OffersMessage> {
|
async function userOffers(): Promise<OffersMessage> {
|
||||||
return [RpcMessageType.Offers, await pg.userOffers(user)];
|
return [RpcMessageType.Offers, await offers.userOffers(user)];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function userWants(): Promise<WantsMessage> {
|
async function userWants(): Promise<WantsMessage> {
|
||||||
@@ -37,7 +37,7 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off
|
|||||||
assert(Status[status], 'unknown status');
|
assert(Status[status], 'unknown status');
|
||||||
|
|
||||||
const offer = await offers.statusSet(user, id, status);
|
const offer = await offers.statusSet(user, id, status);
|
||||||
const userOffers = await pg.userOffers(user);
|
const userOffers = await offers.userOffers(user);
|
||||||
|
|
||||||
return [RpcMessageType.Offers, userOffers]
|
return [RpcMessageType.Offers, userOffers]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { ExternalLink, Check } from 'react-feather';
|
||||||
|
import { useState } from 'preact/hooks';
|
||||||
|
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { State } from './../store';
|
||||||
|
|
||||||
|
import http from '../http';
|
||||||
|
|
||||||
|
function OfferLine(offer) {
|
||||||
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
|
||||||
|
function setStatus(status) {
|
||||||
|
ws.sendOfferStatusSet(offer.id, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accepted = offer.status === 'Accepted';
|
||||||
|
const classes = `${accepted ? 'accepted' : ''}`;
|
||||||
|
const btnText = accepted
|
||||||
|
? offer.status
|
||||||
|
: 'Accept';
|
||||||
|
|
||||||
|
const negBtn = accepted
|
||||||
|
? <button onClick={() => setStatus('Pending')} class='neg'>Cancel</button>
|
||||||
|
: <button onClick={() => setStatus('Declined')} class='neg'>Decline</button>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="line">
|
||||||
|
<span class='number'>${offer.total}</span>
|
||||||
|
<button onClick={() => setStatus('Accepted')} disabled={accepted} class={classes} >{btnText}</button>
|
||||||
|
{negBtn}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function OfferPriceBtn(offer) {
|
||||||
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
const accepted = offer.status === 'Accepted';
|
||||||
|
const confirmed = offer.status === 'Confirmed';
|
||||||
|
|
||||||
|
function setStatus() {
|
||||||
|
if (accepted) return ws.sendOfferStatusSet(offer.id, 'Pending')
|
||||||
|
ws.sendOfferStatusSet(offer.id, 'Accepted');
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = `${accepted ? 'accepted' : ''} ${confirmed ? 'confirmed' : ''}`;
|
||||||
|
const btnText = accepted
|
||||||
|
? offer.status
|
||||||
|
: 'Accept';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button disabled={confirmed} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function WantOffers(wantOffers) {
|
||||||
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
|
||||||
|
const details = wantOffers[0].want.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="offer">
|
||||||
|
<section>
|
||||||
|
<h3>{details.artists_sort} - {details.title} <a href={details.uri} target="_blank"><ExternalLink /></a></h3>
|
||||||
|
<div class='alt-text' >{details.year}</div>
|
||||||
|
<div class='alt-text'>{details.formats.map(f => f.descriptions.join(' ')).join(' ')}</div>
|
||||||
|
</section>
|
||||||
|
<img src={details.thumb} />
|
||||||
|
<div class="lines">
|
||||||
|
{wantOffers.map(o => OfferPriceBtn(o))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ const Info = () => {
|
|||||||
<h2>How It Works</h2>
|
<h2>How It Works</h2>
|
||||||
<p> First, add some records to your <span class='alt'>List</span> on the <a href='/search'>Search Page</a>. </p>
|
<p> First, add some records to your <span class='alt'>List</span> on the <a href='/search'>Search Page</a>. </p>
|
||||||
<p> Mates Crates will then make <span class='alt'>Offers</span> to you for releases you add to your <a href='/list'>List</a>. </p>
|
<p> Mates Crates will then make <span class='alt'>Offers</span> to you for releases you add to your <a href='/list'>List</a>. </p>
|
||||||
|
<p> Select all <span class='alt'>Offers</span> you are interested in, the more you accept the more likely the item will be <span class='alt-text'>Confirmed</span>.</p>
|
||||||
<p> Check back regularly for updated <span class='alt'>Offers</span>, the global record marketplace is a living thing. </p>
|
<p> Check back regularly for updated <span class='alt'>Offers</span>, the global record marketplace is a living thing. </p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -33,12 +34,8 @@ const Info = () => {
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>FAQ</h2>
|
<h2>FAQ</h2>
|
||||||
<h3> Record Condition </h3>
|
|
||||||
<p> All Mates Crates offers are for records that are either mint or very good condition. </p>
|
|
||||||
<h3> Delivery Times </h3>
|
<h3> Delivery Times </h3>
|
||||||
<p> Records are usually available for pickup within a week. We order in bulk, which often means express, insured shipping to Melbourne. </p>
|
<p> Records are usually available for pickup within a week. We order in bulk, which often means express, insured shipping to Melbourne. </p>
|
||||||
<h3> Duplicates </h3>
|
|
||||||
<p> Sometimes we can get a record from multiple sources, these appear as duplicate offers. Accepting multiple offers increases the chance the item will be confirmed. </p>
|
|
||||||
<h3> Friends </h3>
|
<h3> Friends </h3>
|
||||||
<p> Please invite other collectors to the site. More users means offers can become cheaper and shipped sooner. </p>
|
<p> Please invite other collectors to the site. More users means offers can become cheaper and shipped sooner. </p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -6,34 +6,23 @@ import { useSelector, useDispatch } from 'react-redux'
|
|||||||
import http from './../http';
|
import http from './../http';
|
||||||
import { State, offersState } from './../store';
|
import { State, offersState } from './../store';
|
||||||
|
|
||||||
import Offer from './../components/offer';
|
import WantOffers from './../components/want.offers';
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const offers = useSelector((state: State) => state.offers);
|
const offers = useSelector((state: State) => state.offers);
|
||||||
|
|
||||||
useEffect(() => {
|
// if (!offers.length) {
|
||||||
async function getOffers() {
|
// return (
|
||||||
const [error, offers] = await http.getOffers();
|
// <div class={'offers'}>
|
||||||
if (error) return console.error(error);
|
// loading...
|
||||||
dispatch(offersState.actions.set(offers));
|
// </div>
|
||||||
if (offers.length === 0) route('/search');
|
// );
|
||||||
}
|
// }
|
||||||
getOffers();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!offers.length) {
|
|
||||||
return (
|
|
||||||
<div class={'offers'}>
|
|
||||||
loading...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={'offers'}>
|
<div class={'offers'}>
|
||||||
{offers.map(Offer)}
|
{Object.values(offers).map(WantOffers)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-74
@@ -56,7 +56,7 @@ html {
|
|||||||
font-family:var(--base-font-family);
|
font-family:var(--base-font-family);
|
||||||
line-height:var(--base-line-height);
|
line-height:var(--base-line-height);
|
||||||
-webkit-text-size-adjust:100%;
|
-webkit-text-size-adjust:100%;
|
||||||
background:var(--base-background-color);
|
background:var(--background-0);
|
||||||
box-sizing:border-box
|
box-sizing:border-box
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,23 +65,6 @@ html {
|
|||||||
--breakpoint-medium: 35em;
|
--breakpoint-medium: 35em;
|
||||||
--breakpoint-large: 56em;
|
--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;
|
|
||||||
|
|
||||||
--color-blue-gray: #506485;
|
|
||||||
--color-green: #537a34;
|
|
||||||
|
|
||||||
--base-accent-color: var(--color-accent-blue);
|
|
||||||
--fg-color: var(--color-off-white);
|
|
||||||
--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;
|
--small-font-size: 0.75em;
|
||||||
--base-font-size: 1em;
|
--base-font-size: 1em;
|
||||||
--smedium-font-size: 1.333em;
|
--smedium-font-size: 1.333em;
|
||||||
@@ -96,9 +79,6 @@ html {
|
|||||||
--base-line-height: 1.4;
|
--base-line-height: 1.4;
|
||||||
--header-line-height: 1.2;
|
--header-line-height: 1.2;
|
||||||
|
|
||||||
--min-font-size: var(--base-font-size);
|
|
||||||
--max-font-size: var(--large-font-size);
|
|
||||||
|
|
||||||
--base-border-color: var(--secondary-font-color);
|
--base-border-color: var(--secondary-font-color);
|
||||||
--base-border: 2px solid var(--base-border-color);
|
--base-border: 2px solid var(--base-border-color);
|
||||||
--hover-border: 2px solid var(--base-font-color);
|
--hover-border: 2px solid var(--base-font-color);
|
||||||
@@ -107,11 +87,37 @@ html {
|
|||||||
--base-focus: 2px solid var(--base-accent-color);
|
--base-focus: 2px solid var(--base-accent-color);
|
||||||
--base-outline-offset: 0.25em;
|
--base-outline-offset: 0.25em;
|
||||||
|
|
||||||
--base-spacing: 1rem;
|
--min-font-size: var(--base-font-size);
|
||||||
--base-spacing-half: calc(var(--base-spacing)/2);
|
--max-font-size: var(--large-font-size);
|
||||||
--base-spacing-double: calc(var(--base-spacing)*2);
|
|
||||||
|
|
||||||
--utility-bar-header-height: calc(var(--base-spacing) * 1.5);
|
--color-dark-blue: #101014;
|
||||||
|
--color-mid-blue: #16171d;
|
||||||
|
--color-light-blue: #1f2128;
|
||||||
|
--color-mid-grey: #9BA0B3;
|
||||||
|
--color-off-white: #F4F1D0;
|
||||||
|
--color-accent-blue: #5E9ED6;
|
||||||
|
|
||||||
|
--color-blue-gray: #506485;
|
||||||
|
|
||||||
|
--color-blue: #475977;
|
||||||
|
--color-green: #476d4a;
|
||||||
|
|
||||||
|
--color-accepted: var(--color-blue);
|
||||||
|
--color-confirmed: var(--color-green);
|
||||||
|
|
||||||
|
--base-accent-color: var(--color-accent-blue);
|
||||||
|
--fg-color: var(--color-off-white);
|
||||||
|
--hover-link-color: var(--color-off-white);
|
||||||
|
|
||||||
|
--background-0: var(--color-dark-blue);
|
||||||
|
--background-1: var(--color-mid-blue);
|
||||||
|
--background-2: var(--color-light-blue);
|
||||||
|
|
||||||
|
--spacing: 1rem;
|
||||||
|
--spacing-half: calc(var(--spacing)/2);
|
||||||
|
--spacing-double: calc(var(--spacing)*2);
|
||||||
|
|
||||||
|
--utility-bar-header-height: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input, optgroup, select, textarea {
|
button, input, optgroup, select, textarea {
|
||||||
@@ -136,11 +142,11 @@ input:focus, textarea:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button, input {
|
button, input {
|
||||||
overflow:visible
|
overflow:visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, select {
|
button, select {
|
||||||
text-transform:none
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
button, [type=button], [type=reset], [type=submit] {
|
button, [type=button], [type=reset], [type=submit] {
|
||||||
@@ -154,7 +160,7 @@ button, [type=button], [type=reset], [type=submit] {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--tertiary-background-color);
|
background: var(--background-2);
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,33 +171,33 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom:var(--base-spacing);
|
margin-bottom:var(--spacing);
|
||||||
font-size:var(--large-font-size)
|
font-size:var(--large-font-size)
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom:var(--base-spacing);
|
margin-bottom:var(--spacing);
|
||||||
font-size:var(--medium-font-size);
|
font-size:var(--medium-font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-bottom:calc(var(--base-spacing)/2);
|
margin-bottom:calc(var(--spacing)/2);
|
||||||
font-size:var(--smedium-font-size)
|
font-size:var(--smedium-font-size)
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-bottom:calc(var(--base-spacing)/3);
|
margin-bottom:calc(var(--spacing)/3);
|
||||||
font-size:var(--base-font-size)
|
font-size:var(--base-font-size)
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin:0 0 var(--base-spacing)
|
margin:0 0 var(--spacing)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ok actual styles
|
// ok actual styles
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding-top: calc(var(--base-spacing)/2);
|
padding-top: calc(var(--spacing)/2);
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@@ -199,8 +205,8 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button, [type=submit] {
|
button, [type=submit] {
|
||||||
padding: var(--base-spacing-half);
|
padding: var(--spacing-half);
|
||||||
background: var(--secondary-background-color);
|
background: var(--background-1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +228,7 @@ button, [type=submit] {
|
|||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact {
|
.contact {
|
||||||
@@ -237,10 +243,10 @@ button, [type=submit] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
background-color: var(--secondary-background-color);
|
background-color: var(--background-1);
|
||||||
|
|
||||||
button {
|
button {
|
||||||
border-color: var(--tertiary-background-color);
|
border-color: var(--background-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,15 +256,15 @@ article {
|
|||||||
|
|
||||||
article {
|
article {
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: var(--base-spacing);
|
padding: var(--spacing);
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
grid-template-columns: 1fr 4fr 1fr;
|
grid-template-columns: 1fr 4fr 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin-right: var(--base-spacing);
|
margin-right: var(--spacing);
|
||||||
background: var(--tertiary-background-color);
|
background: var(--background-2);
|
||||||
border-color: var(--secondary-background-color);
|
border-color: var(--background-1);
|
||||||
|
|
||||||
&.added {
|
&.added {
|
||||||
background: var(--color-green);
|
background: var(--color-green);
|
||||||
@@ -270,7 +276,10 @@ article {
|
|||||||
.list {
|
.list {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
1fr 1fr 1fr 1fr;
|
1fr 1fr 1fr;
|
||||||
|
|
||||||
|
grid-row-gap: var(--spacing);
|
||||||
|
grid-column-gap: var(--spacing);
|
||||||
|
|
||||||
article {
|
article {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -284,6 +293,7 @@ article {
|
|||||||
"remove remove";
|
"remove remove";
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
margin-top: var(--spacing-half);
|
||||||
grid-area: remove
|
grid-area: remove
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,13 +301,12 @@ article {
|
|||||||
grid-area: details;
|
grid-area: details;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: var(--base-spacing);
|
padding: var(--spacing);
|
||||||
margin: var(--base-spacing-half);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
|
|
||||||
label, input {
|
label, input {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -308,15 +317,15 @@ form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: var(--base-spacing-half);
|
padding: var(--spacing-half);
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-family: var(--secondary-font-family);
|
font-family: var(--secondary-font-family);
|
||||||
font-size: var(--small-font-size);
|
font-size: var(--small-font-size);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-bottom: var(--base-spacing-half);
|
margin-bottom: var(--spacing-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -334,7 +343,7 @@ a {
|
|||||||
nav {
|
nav {
|
||||||
a {
|
a {
|
||||||
font-size: var(--small-font-size);
|
font-size: var(--small-font-size);
|
||||||
margin-right: var(--base-spacing);
|
margin-right: var(--spacing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,7 +351,7 @@ nav {
|
|||||||
header {
|
header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr min-content;
|
grid-template-columns: 1fr 1fr min-content;
|
||||||
padding-bottom: var(--base-spacing);
|
padding-bottom: var(--spacing);
|
||||||
|
|
||||||
a {
|
a {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -353,10 +362,10 @@ header {
|
|||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
|
|
||||||
.bundle {
|
.bundle {
|
||||||
margin-bottom: var(--base-spacing-double);
|
margin-bottom: var(--spacing-double);
|
||||||
|
|
||||||
section {
|
section {
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
}
|
}
|
||||||
|
|
||||||
.items {
|
.items {
|
||||||
@@ -364,14 +373,14 @@ header {
|
|||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
|
||||||
div {
|
div {
|
||||||
margin-right: var(--base-spacing);
|
margin-right: var(--spacing);
|
||||||
margin-bottom: var(--base-spacing);
|
margin-bottom: var(--spacing);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
&.bold {
|
&.bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: var(--tertiary-background-color);
|
background: var(--background-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,13 +394,14 @@ header {
|
|||||||
.offer {
|
.offer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr min-content;
|
grid-template-columns: 1fr min-content;
|
||||||
|
grid-template-rows: 1fr min-content;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"details img"
|
"details img"
|
||||||
"btn neg";
|
"lines lines";
|
||||||
|
|
||||||
padding: var(--base-spacing);
|
padding: var(--spacing);
|
||||||
margin: var(--base-spacing-half);
|
margin: var(--spacing-half);
|
||||||
background: var(--secondary-background-color);
|
background: var(--background-1);
|
||||||
|
|
||||||
section {
|
section {
|
||||||
grid-area: details;
|
grid-area: details;
|
||||||
@@ -400,22 +410,31 @@ header {
|
|||||||
img {
|
img {
|
||||||
grid-area: img;
|
grid-area: img;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
margin-left: var(--base-spacing-half);
|
margin-left: var(--spacing-half);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.lines {
|
||||||
margin-top: var(--base-spacing-half);
|
grid-area: lines;
|
||||||
grid-area: btn;
|
display: flex;
|
||||||
border-color: var(--tertiary-background-color);
|
flex-wrap: wrap;
|
||||||
|
column-gap: var(--spacing-half);
|
||||||
|
|
||||||
&.accepted {
|
button {
|
||||||
background: var(--color-accent-blue);
|
margin-top: var(--spacing-half);
|
||||||
cursor: auto;
|
border-color: var(--background-2);
|
||||||
}
|
|
||||||
|
|
||||||
&.neg {
|
&.accepted {
|
||||||
margin-left: var(--base-spacing-half);
|
background: var(--color-accepted);
|
||||||
grid-area: neg;
|
}
|
||||||
|
|
||||||
|
&.confirmed {
|
||||||
|
cursor: auto;
|
||||||
|
background: var(--color-confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.neg {
|
||||||
|
margin-left: var(--spacing-half);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user