acp stuff
This commit is contained in:
1 parent
deb9eb3fc2
commit
047f606e83
14 files changed
+368
-44
No files matched your search
+19
-1
@@ -33,13 +33,17 @@ api.interceptors.response.use(onSuccess, onError);
|
||||
|
||||
const actions = {
|
||||
getBundles: async function getBundles() {
|
||||
return api.get('/bundles');
|
||||
return api.get('/acp/bundles');
|
||||
},
|
||||
|
||||
getOffers: async function getOffers() {
|
||||
return api.get('/user/offers');
|
||||
},
|
||||
|
||||
getOffersAcp: async function getOffersAcp() {
|
||||
return api.get('/acp/offers');
|
||||
},
|
||||
|
||||
getWants: async function getWants() {
|
||||
return api.get('/want/all');
|
||||
},
|
||||
@@ -74,6 +78,20 @@ const actions = {
|
||||
password,
|
||||
});
|
||||
},
|
||||
|
||||
postOffers: async function postOffers(listingIds, shipping, baseShipping) {
|
||||
return api.post('/offers', {
|
||||
listingIds,
|
||||
shipping,
|
||||
baseShipping,
|
||||
});
|
||||
},
|
||||
|
||||
postOfferAccept: async function postOffers(id) {
|
||||
return api.post('/offer/accept', {
|
||||
id,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default actions;
|
||||
+5
-1
@@ -8,6 +8,7 @@ import Header from './components/header';
|
||||
|
||||
import Home from './routes/home';
|
||||
import Bundles from './routes/bundles';
|
||||
import Admin from './routes/acp';
|
||||
import Login from './routes/login';
|
||||
import Search from './routes/search';
|
||||
import List from './routes/list';
|
||||
@@ -36,10 +37,13 @@ function App() {
|
||||
<Header user={user} />
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/bundles" component={Bundles} />
|
||||
<Route path="/login" component={Login} success={loginSuccess} />
|
||||
<Route path="/search" component={Search} />
|
||||
<Route path="/list" component={List} user={user} />
|
||||
|
||||
{/* ACP */}
|
||||
<Route path="/bundles" component={Bundles} />
|
||||
<Route path="/offers/acp" component={Admin} />
|
||||
</Router>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { h } from 'preact';
|
||||
import { ExternalLink, Check } from 'react-feather';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
import actions from '../actions';
|
||||
|
||||
export default function Offer(offer) {
|
||||
const { want: { data: details } } = offer;
|
||||
|
||||
const [accepted, setAccepted] = useState(offer.status !== 'pending');
|
||||
|
||||
async function onClick() {
|
||||
const [error] = await actions.postOfferAccept(offer.id);
|
||||
if (error) return false;
|
||||
setAccepted(true);
|
||||
}
|
||||
|
||||
const classes = `${accepted ? 'accepted' : ''}`;
|
||||
const btnText = accepted
|
||||
? offer.status
|
||||
: 'Accept';
|
||||
|
||||
return (
|
||||
<div class="offer">
|
||||
<section>
|
||||
<h2>{details.artists_sort} - {details.title} <a href={details.uri} target="_blank"><ExternalLink /></a></h2>
|
||||
<h5 class='alt-text' >{details.year}</h5>
|
||||
<h5 class='alt-text'>{details.formats[0].descriptions.join(' ')}</h5>
|
||||
<h5 class='number'>${offer.total}</h5>
|
||||
</section>
|
||||
<img src={details.thumb} />
|
||||
<button onClick={onClick} disabled={accepted} class={classes} >{btnText}</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,59 @@
|
||||
import { h } from 'preact';
|
||||
import { Square, CheckSquare } from 'react-feather';
|
||||
import { useState, useReducer } from 'preact/hooks';
|
||||
|
||||
import actions from '../actions';
|
||||
|
||||
function Item(item, state, dispatch) {
|
||||
const icon = state.checkedIds.includes(item.id)
|
||||
? <CheckSquare onClick={() => dispatch({ id: item.id})} />
|
||||
: <Square onClick={() => dispatch({ id: item.id})} />;
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
<a href={item.uri} target="_blank" >
|
||||
<h4>{item.description}</h4>
|
||||
</a>
|
||||
<h5 class='alt-text'>{item.price.toFixed(2)} {item.currency}</h5>
|
||||
{icon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const reducer = (state, action) => {
|
||||
if (state.checkedIds.includes(action.id)) {
|
||||
return {
|
||||
...state,
|
||||
checkedIds: state.checkedIds.filter(id => id !== action.id)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
checkedIds: [
|
||||
...state.checkedIds,
|
||||
action.id
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default function Bundle(bundle) {
|
||||
const initialState = { checkedIds: bundle.items.map(i => i.id) }
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
|
||||
const [shipping, setShipping] = useState(0);
|
||||
const [baseShipping, setBaseShipping] = useState(0);
|
||||
|
||||
const items = bundle.items.map(item => Item(item, state, dispatch));
|
||||
|
||||
// 100.71
|
||||
// 33.08
|
||||
|
||||
async function submit(e) {
|
||||
e.preventDefault();
|
||||
const [error, offers] = await actions.postOffers(state.checkedIds, shipping, baseShipping);
|
||||
console.log(error, offers);
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="bundle">
|
||||
@@ -15,28 +67,20 @@ export default function Bundle(bundle) {
|
||||
<form action="">
|
||||
<div class="item">
|
||||
<label for="shipping">Full Shipping Cost</label>
|
||||
<input type="number" name="shipping" id="shipping" required />
|
||||
<input type="number" name="shipping" id="shipping" value={shipping} onChange={(e) => setShipping(Number.parseFloat(e.target.value))} step="0.01" min="0" required />
|
||||
</div>
|
||||
<div class="item">
|
||||
<label for="baseShipping">Base Shipping Cost</label>
|
||||
<input type="number" name="baseShipping" id="baseShipping" required />
|
||||
<input type="number" name="baseShipping" id="baseShipping" value={baseShipping} onChange={(e) => setBaseShipping(Number.parseFloat(e.target.value))} step="0.01" min="0" required />
|
||||
</div>
|
||||
<div class="item">
|
||||
<input type="submit" value="Generate Offers" />
|
||||
<input type="submit" value="Generate Offers" onClick={(e) => submit(e)} />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<div class='items'>
|
||||
{bundle.items.map(i => (
|
||||
<div key={i.id}>
|
||||
<a href={i.uri} target="_blank" >
|
||||
<h4>{i.description}</h4>
|
||||
</a>
|
||||
<h5>{i.user.name}</h5>
|
||||
<h5>{i.price.toFixed(2)} {i.currency}</h5>
|
||||
</div>
|
||||
))}
|
||||
{items}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,16 +5,24 @@ const Header = ({ user }) => {
|
||||
const nav = () => (
|
||||
<nav>
|
||||
<a href="/">Offers</a>
|
||||
<a href="/bundles">Bundles</a>
|
||||
<a href='/list'>List</a>
|
||||
<a href="/search">Search</a>
|
||||
</nav>
|
||||
);
|
||||
|
||||
const adminNav = () => (
|
||||
<nav>
|
||||
<a href="/bundles">Bundles</a>
|
||||
<a href='/offers/acp'>Offers ACP</a>
|
||||
</nav>
|
||||
|
||||
)
|
||||
|
||||
return (
|
||||
<header>
|
||||
{user ? nav() : null}
|
||||
<div></div>
|
||||
{user ? adminNav() : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
import { h } from 'preact';
|
||||
import { ExternalLink } from 'react-feather';
|
||||
import { ExternalLink, Check } from 'react-feather';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
import actions from '../actions';
|
||||
|
||||
export default function Offer(offer) {
|
||||
const { want: { data: details } } = offer;
|
||||
|
||||
const [accepted, setAccepted] = useState(offer.status !== 'pending');
|
||||
|
||||
async function onClick() {
|
||||
const [error] = await actions.postOfferAccept(offer.id);
|
||||
if (error) return false;
|
||||
setAccepted(true);
|
||||
}
|
||||
|
||||
const classes = `${accepted ? 'accepted' : ''}`;
|
||||
const btnText = accepted
|
||||
? offer.status
|
||||
: 'Accept';
|
||||
|
||||
return (
|
||||
<div class="offer">
|
||||
<h2>{details.artists_sort} - {details.title} <a href={details.uri} target="_blank"><ExternalLink /></a></h2>
|
||||
<h5 class='alt-text' >{details.year}</h5>
|
||||
<h5 class='alt-text'>{details.formats[0].descriptions.join(' ')}</h5>
|
||||
<section>
|
||||
<h2>{details.artists_sort} - {details.title} <a href={details.uri} target="_blank"><ExternalLink /></a></h2>
|
||||
<h5 class='alt-text' >{details.year}</h5>
|
||||
<h5 class='alt-text'>{details.formats[0].descriptions.join(' ')}</h5>
|
||||
<h5 class='number'>${offer.total}</h5>
|
||||
</section>
|
||||
<img src={details.thumb} />
|
||||
<h5>${offer.total}</h5>
|
||||
<button onClick={onClick} disabled={accepted} class={classes} >{btnText}</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { h } from 'preact';
|
||||
import { useState, useEffect } from 'preact/hooks';
|
||||
|
||||
import actions from './../actions';
|
||||
import AcpOffer from './../components/acp.offer';
|
||||
|
||||
function Item(item) {
|
||||
|
||||
console.log(item);
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
<a href={item.listing.data.uri} target="_blank" >
|
||||
<h4>{item.listing.data.release.description}</h4>
|
||||
</a>
|
||||
<h5 class='alt-text'>{item.total}</h5>
|
||||
<h5>{item.status}</h5>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SellerGroup(group) {
|
||||
return (
|
||||
<div class="bundle">
|
||||
<section>
|
||||
<h2>{group.username} - {group.seller}</h2>
|
||||
<h3>{group.offers.length} offers</h3>
|
||||
<h4>Ships: {group.mentionsAustralia && 'AU'} {group.mentionsWorldwide && 'WORLD'}</h4>
|
||||
</section>
|
||||
|
||||
<div class='items'>
|
||||
{group.offers.map(Item)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Home() {
|
||||
const [offers, setOffers] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getOffers() {
|
||||
const [error, offers] = await actions.getOffersAcp();
|
||||
if (error) return console.error(error);
|
||||
setOffers(offers);
|
||||
}
|
||||
getOffers();
|
||||
}, []);
|
||||
|
||||
if (!offers.length) {
|
||||
return (
|
||||
<div class={'offers'}>
|
||||
loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div class='bundles'>
|
||||
{offers.map(SellerGroup)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@@ -19,7 +19,7 @@ ol, ul {
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
// max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ button, [type=submit] {
|
||||
|
||||
.alt-text {
|
||||
font-family: var(--secondary-font-family);
|
||||
font-size: var(--small-font-size);
|
||||
// font-size: var(--small-font-size);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -279,8 +279,12 @@ a {
|
||||
|
||||
header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 1fr;
|
||||
grid-template-columns: 1fr 1fr min-content;
|
||||
padding-bottom: var(--base-spacing);
|
||||
|
||||
a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.bundles {
|
||||
@@ -289,7 +293,7 @@ header {
|
||||
.bundle {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
margin-bottom: var(--base-spacing);
|
||||
margin-bottom: var(--base-spacing-double);
|
||||
|
||||
.items {
|
||||
grid-column: 1 / -1;
|
||||
@@ -300,6 +304,9 @@ header {
|
||||
div {
|
||||
margin-right: var(--base-spacing);
|
||||
margin-bottom: var(--base-spacing);
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,5 +315,39 @@ header {
|
||||
.offers {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
.offer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"details img"
|
||||
"btn btn";
|
||||
|
||||
padding: var(--base-spacing);
|
||||
margin: var(--base-spacing-half);
|
||||
background: var(--secondary-background-color);
|
||||
|
||||
section {
|
||||
grid-area: details;
|
||||
}
|
||||
|
||||
img {
|
||||
grid-area: img;
|
||||
justify-self: end;
|
||||
margin-left: var(--base-spacing-half);
|
||||
}
|
||||
|
||||
button {
|
||||
grid-area: btn;
|
||||
|
||||
&.accepted {
|
||||
background: var(--color-accent-blue);
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: var(--secondary-font-family);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
Reference in new issue
Block a user