Merge branch 'master' of ssh://git.mnml.gg:40022/home/ntr/mcrates into sample-offers

This commit is contained in:
ntr committed 2022-12-02 09:22:25 +11:00
commit 2f8c883d75
19 files changed
+192 -87

No files matched your search

+4 -1
View File
@@ -1,5 +1,9 @@
# TODO # TODO
contact options on landing page
move want add to rpc
listing ids overflowing listing ids overflowing
skip unofficial release skip unofficial release
@@ -9,7 +13,6 @@ status stepper
queue up wantlist adds to gracefully handle rate limiting queue up wantlist adds to gracefully handle rate limiting
push offers push offers
cover off admin routes and commands
listen / notify listen / notify
on offers on offers
+1 -1
View File
@@ -28,7 +28,7 @@ async function start() {
events, events,
} = createServices(log); } = createServices(log);
events.emit(Events.StartupSync); // events.emit(Events.Sync);
// events.emit(Events.ListingsUpdated); // events.emit(Events.ListingsUpdated);
api.listen(); api.listen();
+1 -16
View File
@@ -63,13 +63,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
ctx.status = 200; ctx.status = 200;
}); });
router.get('/user/offers', async function userOffersGet(ctx, next) {
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
const user = ctx.state.user;
const offers = await pg.userOffers(user);
ctx.response.body = offers;
});
router.post('/want', async function wantPost(ctx, next) { router.post('/want', async function wantPost(ctx, next) {
ctx.assert(ctx.isAuthenticated(), 401, 'log in required'); ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
const { releaseId } = ctx.request.body; const { releaseId } = ctx.request.body;
@@ -89,14 +82,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
ctx.response.body = wants; ctx.response.body = wants;
}); });
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 wantSearch(ctx, next) { router.post('/want/search', async function wantSearch(ctx, next) {
const { query } = ctx.request.body; const { query } = ctx.request.body;
const results = await discogs.search(query); const results = await discogs.search(query);
@@ -122,7 +107,7 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
// log.child... // log.child...
const ws = ctx.websocket; const ws = ctx.websocket;
const rpc = createRpc(log, pg, ws, discogs, offers, ctx.state.user as User | null); const rpc = createRpc(log, pg, ws, discogs, offers, events, ctx.state.user as User | null);
if (ctx.state.user) events.emit(Events.Connection, { ws, id: ctx.state.user.id }); if (ctx.state.user) events.emit(Events.Connection, { ws, id: ctx.state.user.id });
ws.on('message', async function incoming(message) { ws.on('message', async function incoming(message) {
+1 -1
View File
@@ -48,7 +48,7 @@ export default function createDiscogs(log: Log, pg: Pg) {
// @ts-ignore // @ts-ignore
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value); const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
log.info(additions); log.info('want list additions', additions);
return additions; return additions;
} }
+4 -3
View File
@@ -10,7 +10,7 @@ import routerAuth from './routers/router.auth';
export enum Events { export enum Events {
WantsAdded = 'WantsAdded', WantsAdded = 'WantsAdded',
StartupSync = 'StartupSync', Sync = 'Sync',
ListingsUpdated = 'ListingsUpdated', ListingsUpdated = 'ListingsUpdated',
Connection = 'Connection', Connection = 'Connection',
} }
@@ -51,11 +51,12 @@ 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.Sync, async function sync() {
const wants = await pg.wantsToSync(); const wants = await pg.wantsToSync();
log.info({ event: Events.StartupSync, wants: wants.length }); log.info({ event: Events.Sync, wants: wants.length });
await updateWantListings(wants); await updateWantListings(wants);
events.emit(Events.ListingsUpdated); events.emit(Events.ListingsUpdated);
}); });
+1 -2
View File
@@ -51,8 +51,7 @@ export default function createOffers(log: Log, pg: Pg) {
// TODO probably need to take one of each condition of record from a store // TODO probably need to take one of each condition of record from a store
// eg. they have a VG+ and a M we should make offers for each // eg. they have a VG+ and a M we should make offers for each
// log.info(sorted); log.trace('generated offers', sorted);
const inserts = await pg.offersUpdate(sorted); const inserts = await pg.offersUpdate(sorted);
return inserts; return inserts;
+29 -7
View File
@@ -1,5 +1,3 @@
import { EventEmitter } from 'node:events';
import { setTimeout } from 'timers/promises';
import assert from 'assert/strict'; import assert from 'assert/strict';
import type Log from 'bunyan'; import type Log from 'bunyan';
@@ -12,6 +10,7 @@ import type { Offers } from './offers';
import { Status } from './offers'; import { Status } from './offers';
import { isAdmin } from './utils'; import { isAdmin } from './utils';
import { Events } from './events';
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType { function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
if (RpcMessageType[msgType]) { if (RpcMessageType[msgType]) {
@@ -21,7 +20,7 @@ function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
} }
// rpc is created per connection from the api // rpc is created per connection from the api
export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers: Offers, user: User | null) { export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers: Offers, events, user: User | null) {
async function ping(): Promise<PongMessage> { async function ping(): Promise<PongMessage> {
return [RpcMessageType.Pong, []]; return [RpcMessageType.Pong, []];
} }
@@ -71,6 +70,12 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
return [RpcMessageType.AcpOffers, await offers.acpOffers()]; return [RpcMessageType.AcpOffers, await offers.acpOffers()];
} }
async function acpListingsSync(): Promise<AcpListingsSyncMessage> {
assert(isAdmin(user), 'not authorised');
events.emit(Events.Sync);
return [RpcMessageType.AcpListingsSync, []];
}
async function receive(message: any): Promise<RpcMessage> { async function receive(message: any): Promise<RpcMessage> {
const [msgType, params] = message; const [msgType, params] = message;
@@ -101,6 +106,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
return acpOfferStatusSet(params); return acpOfferStatusSet(params);
case RpcMessageType.AcpOffers: case RpcMessageType.AcpOffers:
return acpOffers(); return acpOffers();
case RpcMessageType.AcpListingsSync:
return acpListingsSync();
default: default:
throw new Error(`Message type ${msgType} is known but unhandled`); throw new Error(`Message type ${msgType} is known but unhandled`);
@@ -112,18 +119,30 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
throw new Error(`Invalid parameters for message type ${msgType}: ${JSON.stringify(message)}`); throw new Error(`Invalid parameters for message type ${msgType}: ${JSON.stringify(message)}`);
} }
async function anonConnection() {
}
async function adminConnection() {
// don't need it, requested on route
// send(await acpOffers());
}
async function onConnection() { async function onConnection() {
if (!user) return anonConnection();
const userMsg: UserMessage = [RpcMessageType.User, user]; const userMsg: UserMessage = [RpcMessageType.User, user];
send(userMsg); send(userMsg);
const userOffers = await offers.userOffers(user);
const offersMsg: OffersMessage = [RpcMessageType.Offers, userOffers];
send(offersMsg);
const wants = await pg.userWants(user); const wants = await pg.userWants(user);
const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants]; const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants];
send(wantsMsg); send(wantsMsg);
const samples = await offers.samples(); if (isAdmin(user)) return adminConnection();
const sampleMsg: OfferSamplesMessage = [RpcMessageType.OfferSamples, samples];
// console.log(samples);
send(sampleMsg);
} }
function send(msg: RpcMessage) { function send(msg: RpcMessage) {
@@ -163,6 +182,7 @@ export enum RpcMessageType {
AcpOfferStatusSet = 'AcpOfferStatusSet', AcpOfferStatusSet = 'AcpOfferStatusSet',
AcpOffers = 'AcpOffers', AcpOffers = 'AcpOffers',
AcpListingsSync = 'AcpListingsSync',
WantRm = 'WantRm', WantRm = 'WantRm',
} }
@@ -179,6 +199,7 @@ type OfferSamplesMessage = [RpcMessageType.OfferSamples, Offer[]];
type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]]; type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]];
type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]]; type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]];
type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []];
export type RpcMessage = export type RpcMessage =
PingMessage | PingMessage |
@@ -195,6 +216,7 @@ export type RpcMessage =
AcpOfferStatusSetMessage | AcpOfferStatusSetMessage |
AcpOffersMessage | AcpOffersMessage |
AcpListingsSyncMessage |
ErrorMessage; ErrorMessage;
+11
View File
@@ -10,6 +10,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^1.8.5", "@reduxjs/toolkit": "^1.8.5",
"izitoast": "^1.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"logrocket": "^3.0.1", "logrocket": "^3.0.1",
"preact": "^10.10.0", "preact": "^10.10.0",
@@ -10844,6 +10845,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/izitoast": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/izitoast/-/izitoast-1.4.0.tgz",
"integrity": "sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w=="
},
"node_modules/jake": { "node_modules/jake": {
"version": "10.8.5", "version": "10.8.5",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
@@ -29566,6 +29572,11 @@
"istanbul-lib-report": "^3.0.0" "istanbul-lib-report": "^3.0.0"
} }
}, },
"izitoast": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/izitoast/-/izitoast-1.4.0.tgz",
"integrity": "sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w=="
},
"jake": { "jake": {
"version": "10.8.5", "version": "10.8.5",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
+1
View File
@@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^1.8.5", "@reduxjs/toolkit": "^1.8.5",
"izitoast": "^1.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"logrocket": "^3.0.1", "logrocket": "^3.0.1",
"preact": "^10.10.0", "preact": "^10.10.0",
+1 -1
View File
@@ -19,7 +19,7 @@ import Info from './routes/info';
if (window.location.host === 'matescrates.au') LogRocket.init('yh0dy3/mcrates'); if (window.location.host === 'matescrates.au') LogRocket.init('yh0dy3/mcrates');
const Main = () => { const Main = () => {
const dispatch = useDispatch() const dispatch = useDispatch();
// i have no idea why this is needed, // i have no idea why this is needed,
// but it makes sure the user changes actually rerender // but it makes sure the user changes actually rerender
+4 -15
View File
@@ -32,21 +32,7 @@ function onError(error) {
api.interceptors.response.use(onSuccess, onError); api.interceptors.response.use(onSuccess, onError);
const actions = { const actions = {
getBundles: async function getBundles() { // TODO move to rpc
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');
},
postWantSearch: async function postWantSearch(query) { postWantSearch: async function postWantSearch(query) {
return api.post('/want/search', { query }); return api.post('/want/search', { query });
@@ -60,6 +46,9 @@ const actions = {
return api.post('/want/discogs', { username }); return api.post('/want/discogs', { username });
}, },
// --------------------------------
// require as http to set cookies
getLogin: async function getLogin() { getLogin: async function getLogin() {
return api.get('/auth/login') return api.get('/auth/login')
}, },
+7 -3
View File
@@ -70,9 +70,9 @@ function Home() {
const offers = useSelector((state: State) => state.acpOffers); const offers = useSelector((state: State) => state.acpOffers);
const ws = useSelector((state: State) => state.ws); const ws = useSelector((state: State) => state.ws);
// useEffect(() => { function sync() {
// ws.sendAcpOffers(); ws.sendAcpListingsSync();
// }, [offers, ws]); }
if (!offers.length) { if (!offers.length) {
return ( return (
@@ -83,9 +83,13 @@ function Home() {
} }
return ( return (
<div>
<button onClick={sync}> sync listings </button>
<hr />
<div class='bundles'> <div class='bundles'>
{offers.map(SellerGroup)} {offers.map(SellerGroup)}
</div> </div>
</div>
); );
} }
+26 -19
View File
@@ -12,12 +12,13 @@ const Login = ({ mode }) => {
const user = useSelector((state: State) => state.user); const user = useSelector((state: State) => state.user);
const ws = useSelector((state: State) => state.ws); const ws = useSelector((state: State) => state.ws);
const [email, setEmail] = useState('')
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
async function postLogin() { function gotoRegister() {
return route('/register');
}
async function postLogin(email, password) {
const [error, user] = await http.postLogin(email, password); const [error, user] = await http.postLogin(email, password);
if (error) return setError(error); if (error) return setError(error);
route('/'); route('/');
@@ -26,17 +27,23 @@ const Login = ({ mode }) => {
ws.connect(); ws.connect();
} }
async function postRegister() { async function postRegister(email, password, confirm) {
if (password !== confirm) return setError('passwords do not match');
const [error, user] = await http.postRegister(email, password); const [error, user] = await http.postRegister(email, password);
if (error) return setError(error); if (error) return setError(error);
route('/search'); route('/search');
dispatch(userState.actions.set(user)); dispatch(userState.actions.set(user));
// reconnect ws to get logged in methods
ws.connect();
} }
function handleSubmit(e) { function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
if (mode === 'login') return postLogin()
if (mode === 'register') return postRegister(); console.log(e.target.email, e.target.password, e.target.confirm);
if (mode === 'login') return postLogin(e.target.email.value, e.target.password.value);
if (mode === 'register') return postRegister(e.target.email.value, e.target.password.value, e.target.confirm.value);
} }
const emailRef = useRef<HTMLInputElement>() const emailRef = useRef<HTMLInputElement>()
@@ -52,21 +59,21 @@ const Login = ({ mode }) => {
if (mode === 'register') return ( if (mode === 'register') return (
<div class="login"> <div class="login">
{errorEl} {errorEl}
<form action=""> <form action="" onSubmit={handleSubmit}>
<div class="item"> <div class="item">
<label for="email">email</label> <label for="email">email</label>
<input type="email" name="email" id="email" ref={emailRef} value={email} required onInput={(e) => setEmail((e.target as HTMLInputElement).value)} /> <input type="email" name="email" id="email" ref={emailRef} required />
</div> </div>
<div class="item"> <div class="item">
<label for="password">password</label> <label for="password">password</label>
<input type="password" name="password" id="password" value={password} required onInput={(e) => setPassword((e.target as HTMLInputElement).value)} /> <input type="password" name="password" id="password" required />
</div> </div>
<div class="item"> <div class="item">
<label for="password">confirm password</label> <label for="password">confirm password</label>
<input type="password" name="password" id="password" value={confirmPassword} required onInput={(e) => setConfirmPassword((e.target as HTMLInputElement).value)} /> <input type="password" name="confirm" id="confirm" required />
</div> </div>
<div class="item"> <div class="item">
<input type="submit" value="register" onClick={handleSubmit} /> <input type="submit" value="register" />
</div> </div>
</form> </form>
</div> </div>
@@ -76,19 +83,20 @@ const Login = ({ mode }) => {
return ( return (
<div class="login"> <div class="login">
{errorEl} {errorEl}
<form action=""> <form action="" onSubmit={handleSubmit}>
<div class="item"> <div class="item">
<label for="email">email</label> <label for="email">email</label>
<input type="email" name="email" id="email" ref={emailRef} value={email} required onInput={(e) => setEmail((e.target as HTMLInputElement).value)} /> <input type="email" name="email" id="email" ref={emailRef} />
</div> </div>
<div class="item"> <div class="item">
<label for="password">password</label> <label for="password">password</label>
<input type="password" name="password" id="password" value={password} required onInput={(e) => setPassword((e.target as HTMLInputElement).value)} /> <input type="password" name="password" id="password" />
</div> </div>
<div class="item"> <div class="item">
<input type="submit" value="login" onClick={handleSubmit} /> <input type="submit" value="login"/>
</div> </div>
</form> </form>
<button onClick={gotoRegister}> create account </button>
</div> </div>
); );
}; };
@@ -97,12 +105,11 @@ function LoginPage({ mode }) {
return ( return (
<div class="landing"> <div class="landing">
<Login mode={mode} /> <Login mode={mode} />
<div></div>
<div class='text'>
<Swirl /> <Swirl />
<div class='words'>
<p>Mates Crates is a record importing service.</p> <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>We bundle orders together from thousands of online stores to reduce the cost of shipping vinyl.</p>
<p>Based in Preston.</p> <p>Records can then be picked up from Preston.</p>
</div> </div>
</div> </div>
) )
+2
View File
@@ -16,6 +16,8 @@ function Home() {
route('/search'); route('/search');
} }
if (!offers) return <div>loading...</div>;
return ( return (
<div class={'offers'}> <div class={'offers'}>
{offers.map(OfferGroup)} {offers.map(OfferGroup)}
+11 -5
View File
@@ -1,10 +1,13 @@
import { h } from 'preact'; import { h } from 'preact';
import { useState, useRef, useEffect } from 'preact/hooks'; import { useState, useRef, useEffect } from 'preact/hooks';
import { ExternalLink, Check } from 'react-feather'; import { ExternalLink, Check } from 'react-feather';
import { useSelector } from 'react-redux'
import { State } from './../store';
import http from '../http'; import http from '../http';
function Result(r) { function Result(r, wants) {
const [added, setAdded] = useState(false) const [added, setAdded] = useState(false)
async function onClick() { async function onClick() {
@@ -13,7 +16,9 @@ function Result(r) {
setAdded(true); setAdded(true);
} }
const classes = `${added ? 'added' : ''}`; const wanted = wants.some(w => w.release === r.id);
const classes = `${added || wanted ? 'added' : ''}`;
const btnText = added const btnText = added
? <Check /> ? <Check />
: '+'; : '+';
@@ -39,6 +44,8 @@ const User = () => {
const [results, setResults] = useState([]); const [results, setResults] = useState([]);
const [infoText, setInfoText] = useState(''); const [infoText, setInfoText] = useState('');
const wants = useSelector((state: State) => state.wants);
async function fetchResults() { async function fetchResults() {
const [error, results] = await http.postWantSearch(query); const [error, results] = await http.postWantSearch(query);
@@ -104,10 +111,9 @@ const User = () => {
<hr /> <hr />
<p>Data provided by Discogs.</p> <p>Data provided by Discogs.</p>
</div> </div>
<div /> <div class='results'>
<div>
{infoEl} {infoEl}
{results.map(Result)} {results.map(r => Result(r, wants))}
{topEl} {topEl}
</div> </div>
</div> </div>
+2 -1
View File
@@ -11,7 +11,7 @@ export const userState = createSlice({
export const offersState = createSlice({ export const offersState = createSlice({
name: 'offers', name: 'offers',
initialState: [], initialState: null,
reducers: { reducers: {
set: (state, action) => action.payload set: (state, action) => action.payload
}, },
@@ -52,6 +52,7 @@ const reducer = combineReducers({
const store = createStore( const store = createStore(
reducer, reducer,
// @ts-ignore -- devtools window exists on dev
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); );
+67 -9
View File
@@ -201,7 +201,7 @@ body {
} }
main { main {
padding: 0 5% 0 5%; padding: 0 var(--spacing-double) 0 var(--spacing-double);
} }
button, [type=submit] { button, [type=submit] {
@@ -212,19 +212,27 @@ button, [type=submit] {
.landing { .landing {
display: grid; display: grid;
grid-template-columns: 1fr 0.5fr 2fr;
grid-template-columns: 1fr 2fr;
grid-gap: calc(var(--spacing-double) * 2);
grid-template-areas:
"login swirl"
"login words";
.login { .login {
grid-area: login;
button { button {
width: 100%; width: 100%;
} }
} }
.swirl { .swirl {
grid-area: swirl;
height: 15rem; height: 15rem;
} }
.text { .words {
grid-area: words;
text-align: center; text-align: center;
} }
} }
@@ -274,13 +282,18 @@ article {
.search { .search {
display: grid; display: grid;
grid-template-columns: 1fr 0.5fr 2fr; grid-template-columns: 1fr 2fr;
.results {
display: grid;
padding-left: var(--spacing);
grid-gap: var(--spacing);
article { article {
display: grid; display: grid;
padding: var(--spacing); padding: var(--spacing);
margin-bottom: var(--spacing); grid-template-columns: 1fr 4fr min-content;
grid-template-columns: 1fr 4fr 1fr; }
} }
button { button {
@@ -300,8 +313,7 @@ article {
grid-template-columns: grid-template-columns:
1fr 1fr 1fr; 1fr 1fr 1fr;
grid-row-gap: var(--spacing); grid-gap: var(--spacing);
grid-column-gap: var(--spacing);
article { article {
display: grid; display: grid;
@@ -324,6 +336,10 @@ article {
} }
padding: var(--spacing); padding: var(--spacing);
img {
justify-self: end;
}
} }
} }
@@ -412,6 +428,7 @@ header {
.offers { .offers {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: var(--spacing);
.offer { .offer {
display: grid; display: grid;
@@ -422,7 +439,6 @@ header {
"lines lines"; "lines lines";
padding: var(--spacing); padding: var(--spacing);
margin: var(--spacing-half);
background: var(--background-1); background: var(--background-1);
.details { .details {
@@ -483,3 +499,45 @@ header {
margin-bottom: var(--spacing); margin-bottom: var(--spacing);
} }
@media (max-width:900px) {
.landing {
grid-template-columns: 1fr;
grid-template-areas:
"swirl"
"login"
"words";
}
.list {
grid-template-columns: 1fr;
}
.search {
grid-template-columns: 1fr;
}
.offers {
grid-template-columns: 1fr;
}
.info {
grid-template-columns: 1fr;
// is necessary for some reason
grid-template-areas:
"swirl"
"contact"
"words";
}
main {
padding: 0 var(--spacing) 0 var(--spacing);
}
}
@media (max-width:1200px) {
img {
width: 5rem;
}
}
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>matescrates.au - record import service</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<% preact.headEnd %>
</head>
<body>
<% preact.bodyEnd %>
</body>
</html>
+5 -3
View File
@@ -50,6 +50,10 @@ export default function createSocket(dispatch) {
send(['AcpOfferStatusSet', { id, status }]); send(['AcpOfferStatusSet', { id, status }]);
} }
function sendAcpListingsSync() {
send(['AcpListingsSync', {}]);
}
let pongTimeout; let pongTimeout;
function onPong() { function onPong() {
pongTimeout = setTimeout(sendPing, 5000); pongTimeout = setTimeout(sendPing, 5000);
@@ -111,9 +115,6 @@ export default function createSocket(dispatch) {
function onOpen() { function onOpen() {
console.log('websocket connected', SOCKET_URL) console.log('websocket connected', SOCKET_URL)
sendPing(); sendPing();
sendAcpOffers();
sendOffers();
sendWants();
} }
function onError(event) { function onError(event) {
@@ -157,5 +158,6 @@ export default function createSocket(dispatch) {
sendAcpOffers, sendAcpOffers,
sendAcpOfferStatusSet, sendAcpOfferStatusSet,
sendAcpListingsSync,
}; };
} }