import { render } from "solid-js/web"; import { createState, createSignal, reconcile, createMemo } from "solid-js"; import { req, on } from './websocket'; import { create_deferred, c, Null } from './util'; import { state, setState } from './store'; import storage from 'kv-storage-polyfill'; import './index.css'; import NotFound from './pages/NotFound'; import NotAuthorized from './pages/NotAuthorized'; import NotAuthenticated from './pages/NotAuthenticated'; import Home from './pages/Home'; import User from './pages/User'; import Users from './pages/Users'; import Products from './pages/Products'; onpopstate = function() { setState('path', location.pathname.substring(1)); } if(history.state === null) { history.replaceState({}, null); } function atHome() {return state.path.length === 0} function atCart() {return state.path === 'cart/'} const reservationRegex = /^\/((?<id>\d+)\/)?reservation\/$/; const spyRegex = /^\/(?<id>\d+)$/; const fuckRegex = /^((store\/(?<sid>\d+)(-(?<sslug>[A-Za-z0-9_]+))?\/(?<thing>product|review))|product)\/(?<rest>.*)$/; const somethingRegex = /^(?<id>\d+)(-(?<slug>[A-Za-z0-9_]+))?(\/edit)?$/; const atSpecificStoreRegex = /^(?<id>\d+)(-(?<slug>[A-Za-z0-9_]+))?\/(?<what>.+)/; render(function() { const [wsSuccess, setWsSuccess] = createSignal(null); //disconnected, store, user are the three possible first events on('disconnected', function() { if(wsSuccess() === null) { setWsSuccess(false); } }); on('store', function(how, data) { if(wsSuccess() === null) { setWsSuccess(true); } switch(how) { case 'replace': setState('stores', reconcile(data, {key: 'product_id'})); break; case 'add': setState('stores', state.stores.length, data); break; case 'remove': setState('stores', stores => stores.filter(store => store.id !== data)); break; case 'update': setState('stores', state.stores.findIndex(store => store.id === data.id), data);//I think we do not want reconcile here since this is partial... break; } }); on('user', function(how, data, why) { if(why) {//&& wsSuccess() so that alert isn't the first thing user sees on navigating to the site. up for discussion alert(why); } if(how === 'replace' && data !== null) { setState('user', reconcile(data)); } else {//update || null setState('user', data); } if(wsSuccess() === null) { setWsSuccess(true); } }); let email; const [passphrase, setPassphrase] = createSignal(''); let remember_me; const [pp_visible, set_pp_visible] = createSignal(false); const [action, setAction] = createSignal('login'); const atUser = createMemo(() => state.path.indexOf('user') === 0 ? state.path.substring(4) : false, false, true); const atReservations = createMemo(() => { const a = atUser(); if(a === false) { return undefined; } else { return a.match(reservationRegex)?.groups; } }, undefined, (a, b) => a === b || typeof a === 'object' && typeof b === 'object' && a.id === b.id); const spy = createMemo(() => { const a = atUser(); if(a === false) { return undefined; } else { return a.match(spyRegex)?.groups; } }, undefined, (a, b) => a === b || typeof a === 'object' && typeof b === 'object' && a.id === b.id); const fuck = createMemo(() => state.path.match(fuckRegex)?.groups, undefined, (a, b) => a === b || typeof a === 'object' && typeof b === 'object' && a.sid === b.sid && a.sslug === b.sslug && a.thing === b.thing && a.rest === b.rest); const something = createMemo(() => { const f = fuck(); if(f === undefined) { return undefined; } else { return f.rest.match(somethingRegex)?.groups; } }, undefined, (a, b) => a === b || typeof a === 'object' && typeof b === 'object' && a.id === b.id && a.slug === b.slug); const atStore = createMemo(() => state.path.indexOf('store/') === 0 ? state.path.substring(6) : false, false, true); const atSpecificStore = createMemo(() => { const w = atStore(); if(w === false) { return undefined; } else { return w.match(atSpecificStoreRegex)?.groups; } }, undefined, (a, b) => a === b || typeof a === 'object' && typeof b === 'object' && a.id === b.id && a.slug === b.slug && a.what === b.what); const gay = createMemo(() => fuck()?.sid === undefined || fuck()?.rest === 'new' || fuck()?.rest === 'edit' ? Null : Store, Null, true); function login_success() { storage.delete('carts'); setPassphrase(''); } return <Switch fallback={<> <header> <nav> <a class="pull" classList={{wow: atHome()}} onClick={c} href="/">home</a><br /> <a classList={{wow: atCart()}} onClick={c} href="/cart/">cart</a><br /> <a classList={{wow: state.path.indexOf('product') === 0}} onClick={c} href="/product/">products</a><br /> <a classList={{wow: atStore() !== false}} onClick={c} href="/store/">stores</a><br /> <Show when={state.user}> <a classList={{wow: atUser() !== false}} onClick={c} href="/user">{state.user.email}</a><br /> </Show> </nav> <Show when={state.user} fallback={<> <form onsubmit={async e => { e.preventDefault(); switch(action()) { case 'login': case 'register': req(action(), { remember_me: remember_me.checked, email: email.value, passphrase: passphrase(), carts: await storage.get('carts') }).then(login_success, alert); break; case 'login_otp': req('login_otp', { remember_me: remember_me.checked, carts: await storage.get('carts'), otp: passphrase() }).then(login_success, alert); break; case 'request_otp': req('request_otp', {email: email.value}).then(() => { alert('check your email'); setPassphrase(''); }, alert); break; } }}> <label> email <input required={action() !== 'login_otp'} type="email" placeholder="me@domain.com" ref={email} /> </label> <label style="display: flex /*https://philipwalton.github.io/solved-by-flexbox/demos/input-add-ons/ this just makes the button height match and line up with the input field (2px woopdedoo)*/"> passphrase <input class="longplaceholder" type={pp_visible() ? "text" : "password"} placeholder="a long passphrase is both easier to remember and more secure than a password!" value={passphrase()} onInput={e => setPassphrase(e.target.value)} /> <button type="button" onClick={() => set_pp_visible(!pp_visible())}>{pp_visible() ? '👀' : '🙈'}</button> </label> <label><input type="checkbox" ref={remember_me}/>remember me</label> <button onClick={() => setAction('login')}>login</button> <button onClick={() => setAction('register')}>register</button> <button onClick={() => { if(passphrase()) { setAction('login_otp'); } else { setAction('request_otp'); } }}>{passphrase() ? 'login via code from email' : 'send login link to email'}</button> </form> </>}> <button type="button" onClick={() => req('logout')}>logout</button> </Show> </header> <main> <Switch> <Match when={atHome()}><Home /></Match> <Match when={atCart()}><Cart /></Match> <Match when={atUser() !== false}> <Switch fallback={NotFound}> <Match when={atUser().length === 0}> <Switch fallback={NotAuthenticated}> <Match when={state.user}> <User /> </Match> </Switch> </Match> <Match when={atUser() === '/'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god}> <Users /> </Match> <Match when={state.user?.god === false}> <NotAuthorized /> </Match> </Switch> </Match> <Match when={atReservations()}> <Switch fallback={NotAuthenticated}> <Match when={atReservations().id !== undefined && state.user?.god === false}> <NotAuthorized /> </Match> <Match when={atReservations().id !== undefined && state.user?.god || atReservations().id === undefined && state.user}> <User_Reservations id={atReservations().id}/> </Match> </Switch> </Match> <Match when={spy()}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god}> <User id={spy().id} /> </Match> <Match when={state.user?.god === false}><NotAuthorized /></Match> </Switch> </Match> </Switch> </Match> <Match when={fuck()}> <Dynamic component={gay()} id={fuck()?.sid} slug={fuck()?.sslug}> {/*@once*/<Switch fallback={NotFound}> <Match when={fuck()?.thing === 'review' && fuck()?.rest.length === 0}>reviews</Match> <Match when={(fuck()?.thing === 'product' || fuck()?.sid === undefined)}> <Switch fallback={NotFound}> <Match when={fuck()?.rest.length === 0}><Products /></Match> <Match when={something()}><Product id={something().id} />need switch</Match> <Match when={fuck()?.rest === 'new'}>new need switch</Match> <Match when={fuck()?.rest === 'edit'}>edit need switch</Match> </Switch> </Match> </Switch>} </Dynamic> </Match> <Match when={atStore() !== false}> <Switch fallback={NotFound}> <Match when={atStore().length === 0}> <Stores /> </Match> <Match when={atStore() === 'new'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god}><CreateStore /></Match> <Match when={state.user?.god === false}><NotAuthorized /></Match> </Switch> </Match> <Match when={atStore() === 'edit'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god}>bulk / table edit of stores</Match> <Match when={state.user?.god === false}><NotAuthorized /></Match> </Switch> </Match> <Match when={atStore() === 'reservation/'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god}>super aggregated stats to stroke ego</Match> <Match when={state.user?.god === false}><NotAuthorized /></Match> </Switch> </Match> <Match when={atSpecificStore()}> <Switch fallback={NotFound}> <Match when={atSpecificStore().what === 'edit'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god || state.user?.acl?.[atSpecificStore().id]}>form here</Match> <Match when={state.user?.acl?.[atSpecificStore().id] === false}><NotAuthorized /></Match> </Switch> </Match> <Match when={atSpecificStore().what === 'reservation/'}> <Switch fallback={NotAuthenticated}> <Match when={state.user?.god || typeof state.user?.acl?.[atSpecificStore().id] === 'boolean'}>store_reservations id=atSpecificStore().id</Match> <Match when={state.user?.acl?.[atSpecificStore().id] === undefined}><NotAuthorized /></Match> </Switch> </Match> </Switch> </Match> </Switch> </Match> </Switch> </main> <footer> Sticky footer {new Date().getFullYear()} </footer> </>}> <Match when={wsSuccess() === false}> can't connect to backend. nothing is going to work </Match> </Switch> }, document.body);