...
 
Commits (8)
......@@ -36,11 +36,19 @@ const server = awsServerlessExpress.createServer(app)
exports.handler = (event, context) => {
const { succeed } = context;
// Inspecting the result to debug multiple cookie set problem
// Move cookies into multiValueHeaders
context.succeed = (result) => {
const { headers } = result;
console.log('succeed', {headers});
return succeed(result);
const cookies = Object.keys(headers).filter(key => key.toLowerCase() == "set-cookie");
if (cookies.length > 0) {
const multiValueHeaders = {
"Set-Cookie": cookies.map(key => headers[key]),
};
cookies.forEach(key => delete headers[key]);
return succeed({...result, multiValueHeaders});
} else {
return succeed(result);
}
}
awsServerlessExpress.proxy(server, event, context)
}
......
......@@ -13,3 +13,6 @@
from = "/graphql"
to = "https://7qy2kzhdc5cjfckjb5mukm73qu.appsync-api.us-west-2.amazonaws.com/graphql"
status = 200
[dev]
port = 9000
......@@ -20,6 +20,9 @@
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>PAW Prices</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/flatly/bootstrap.min.css" rel="stylesheet" media="(prefers-color-scheme: light)">
<link href="https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css" rel="stylesheet" media="(prefers-color-scheme: dark)">
</head>
<body>
<noscript>
......
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "paw-ui",
"name": "Pawprices frontned",
"icons": [
{
"src": "favicon.ico",
......@@ -8,7 +8,7 @@
"type": "image/png"
}
],
"start_url": "./index.html",
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
......
......@@ -34,7 +34,7 @@ const query = gql`
}
}
}
breakdown {
breakdown(limit: 730) {
zero
one
two
......
import React, {Component} from 'react';
import Amplify from 'aws-amplify';
import {Authenticator} from 'aws-amplify-react';
import awsconfig from './aws-exports';
import Authenticate from 'react-openidconnect';
import OidcSettings from './oidcsettings';
import AppContainer from './AppContainer';
Amplify.configure(awsconfig);
export default class AppWithAuth extends Component {
constructor(props) {
super(props);
this.userLoaded = this.userLoaded.bind(this);
this.userUnLoaded = this.userUnLoaded.bind(this);
this.state = { user: undefined };
}
userLoaded(user) {
if (user)
this.setState({ "user": user });
}
userUnLoaded() {
this.setState({ "user": undefined });
}
NotAuthenticated() {
return <div>You are not authenticated, please click here to authenticate.</div>;
}
render() {
return (
<div>
<Authenticator>
<Authenticate OidcSettings={OidcSettings} userLoaded={this.userLoaded} userunLoaded={this.userUnLoaded} renderNotAuthenticated={this.NotAuthenticated}>
<AppContainer />
</Authenticator>
</Authenticate>
</div>
);
}
......
......@@ -221,7 +221,7 @@ class Available extends PureComponent {
noDataIndication="No units to display"
bootstrap4
hover
condensed={units.length > 25}
condensed
striped></BootstrapTable>
</Card>
);
......
......@@ -22,7 +22,7 @@ const style = styler([
const fortnight = 2 * 7 * 24 * 60 * 60 * 1000;
function History(props) {
const {number, series} = props;
const {number, series, streak} = props;
const [selection, setSelection] = useState(undefined);
const [highlight, setHighlight] = useState(undefined);
......@@ -45,6 +45,7 @@ function History(props) {
<Card>
<Card.Header>{`Rent history for ${number}`}</Card.Header>
<Card.Body>
<p>On the market for the last {streak} days</p>
<Resizable>
<ChartContainer
timeRange={timerange || series.range()}
......
......@@ -15,7 +15,7 @@ const query = gql`
units(number: $unitNumber) {
number
}
rent(UnitNumber: $unitNumber, order: "date") {
rent(UnitNumber: $unitNumber, order: "reverse:date", limit: 999) {
date
min
max
......@@ -25,19 +25,20 @@ const query = gql`
const fillIn = rents => {
const ymd = 'YYYY-MM-DD';
var startDate = rents[0].date;
var endDate = rents[rents.length - 1].date;
var dates = [];
var currDate = moment(startDate).startOf('day');
var lastDate = moment(endDate).startOf('day');
var startDate = rents[rents.length - 1].date;
var endDate = rents[0].date;
const lookup = rents.reduce((acc, rent) => {
const {date} = rent;
return {...acc, [date]: rent};
}, {});
while (lastDate.diff(currDate) > 0) {
var currDate = moment(startDate);
var lastDate = moment(endDate);
var i = 0;
var dates = [];
while (lastDate.diff(currDate, 'days') >= 0) {
const rent = lookup[currDate.format(ymd)];
if (rent) {
const {min, max, date} = rent;
......@@ -52,12 +53,34 @@ const fillIn = rents => {
});
}
currDate.add({days: 1});
if (i++ > 1000) break;
}
dates.push(rents[rents.length - 1]);
dates.sort((a, b) => {
return b.date - a.date;
});
return dates;
};
const calculateStreak = rents => {
if (rents.length < 2) {
return 1;
}
var i = 0;
do {
var currDate = moment(rents[i++].date).startOf('day');
var prevDate = moment(rents[i].date).startOf('day');
if (currDate.diff(prevDate, 'days') > 1) {
break;
}
} while (i + 1 < rents.length);
return i;
};
const HistoryContainer = ({selected}) => {
const variables = {
unitNumber: selected,
......@@ -88,7 +111,9 @@ const HistoryContainer = ({selected}) => {
aggregation: {min: {min: min()}, max: {max: max()}},
});
return <History number={number} series={series} />;
const streak = calculateStreak(rent);
return <History number={number} series={series} streak={streak} />;
};
function mapStateToProps(state, props) {
......
......@@ -17,7 +17,7 @@ export const audits = `query Audits($date: String, $limit: Int, $order: String)
updatedAt
UnitNumber
}
breakdown {
breakdown(limit: 730) {
zero
one
two
......
/*@import url("https://stackpath.bootstrapcdn.com/bootswatch/4.4.1/darkly/bootstrap.min.css") (prefers-color-scheme: dark);*/
@import "~bootswatch/dist/flatly/bootstrap.min.css" (prefers-color-scheme: light);
@import "~bootswatch/dist/darkly/bootstrap.min.css" (prefers-color-scheme: dark);
......@@ -3,18 +3,9 @@ import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import configureStore from './store/configureStore';
// Original react-bootstrap
//import 'bootstrap/dist/css/bootstrap.min.css';
// V4 themes: cerulean cosmo cyborg darkly flatly journal litera lumen lux materia minty pulse sandstone simplex sketchy slate solar spacelab superhero united yeti
// cosmo, darkly are nice
//import 'bootswatch/dist/darkly/bootstrap.min.css';
// Conditional dark/light (darkly/flatly) based on media query
import './index.css'
import AppContainer from './AppContainer';
import registerServiceWorker from './registerServiceWorker';
// import AppContainer from './AppContainer';
import AppWithAuth from './AppWithAuth';
import * as serviceWorker from './serviceWorker';
import client from './graphql/client';
import {ApolloProvider} from 'react-apollo';
......@@ -24,10 +15,13 @@ const store = configureStore();
ReactDOM.render(
<ApolloProvider client={client}>
<Provider store={store}>
<AppContainer />
<AppWithAuth />
</Provider>
</ApolloProvider>,
document.getElementById('root'),
);
registerServiceWorker();
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
const { location } = window;
const { protocol, host } = location;
const OidcSettings = {
authority: `/.netlify/functions/oidc`,
client_id: 'pawprices',
//response_type: 'id_token token',
response_type: 'code',
redirect_uri: `${protocol}//${host}`,
post_logout_redirect_uri: '/',
};
export default OidcSettings;
// In production, we register a service worker to serve assets from local cache.
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)
);
export default function register () {
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new window.URL(process.env.PUBLIC_URL, window.location)
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl)
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl)
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
})
});
}
}
function registerValidSW (swUrl) {
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.')
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker (swUrl) {
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
window.fetch(swUrl)
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl)
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
})
);
});
}
export function unregister () {
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister()
})
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}
This diff is collapsed.