Emi Matchu
0e314482f7
I haven't been running Prettier consistently on things in this project. Now, it's quick-runnable, and I've got it on everything! Also, I just think tabs are the right default for this kind of thing, and I'm glad to get to switch over to it! (In `package.json`.)
419 lines
9.7 KiB
JavaScript
419 lines
9.7 KiB
JavaScript
import React from "react";
|
|
import * as Sentry from "@sentry/react";
|
|
import { Integrations } from "@sentry/tracing";
|
|
import {
|
|
ChakraProvider,
|
|
Box,
|
|
css as resolveCSS,
|
|
extendTheme,
|
|
useColorMode,
|
|
useTheme,
|
|
} from "@chakra-ui/react";
|
|
import { mode } from "@chakra-ui/theme-tools";
|
|
import { ApolloProvider } from "@apollo/client";
|
|
import { BrowserRouter } from "react-router-dom";
|
|
import { css, Global } from "@emotion/react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
|
|
import apolloClient from "./apolloClient";
|
|
|
|
const reactQueryClient = new QueryClient();
|
|
|
|
let theme = extendTheme({
|
|
styles: {
|
|
global: (props) => ({
|
|
body: {
|
|
background: mode("gray.50", "gray.800")(props),
|
|
color: mode("green.800", "green.50")(props),
|
|
transition: "all 0.25s",
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
|
|
// Capture the global styles function from our theme, but remove it from the
|
|
// theme's `styles.global` key. We don't actually want to apply these styles
|
|
// globally, because they conflict with the app's other styles when embedded in
|
|
// e.g. the item page! We'll apply them more carefully in `ScopedCSSReset`.
|
|
//
|
|
// NOTE: We get this from `theme`, instead of just declaring the function
|
|
// separately, because `extendTheme` returns a wrapped version of our function
|
|
// that also returns some of Chakra's own default global styles.
|
|
const globalStyles = theme.styles.global;
|
|
theme.styles.global = {};
|
|
|
|
export default function AppProvider({ children }) {
|
|
React.useEffect(() => setupLogging(), []);
|
|
|
|
return (
|
|
<BrowserRouter>
|
|
<QueryClientProvider client={reactQueryClient}>
|
|
<ApolloProvider client={apolloClient}>
|
|
<ChakraProvider resetCSS={false} theme={theme}>
|
|
<ScopedCSSReset>{children}</ScopedCSSReset>
|
|
</ChakraProvider>
|
|
</ApolloProvider>
|
|
</QueryClientProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|
|
|
|
function setupLogging() {
|
|
Sentry.init({
|
|
dsn: "https://c55875c3b0904264a1a99e5b741a221e@o506079.ingest.sentry.io/5595379",
|
|
autoSessionTracking: true,
|
|
integrations: [
|
|
new Integrations.BrowserTracing({
|
|
beforeNavigate: (context) => ({
|
|
...context,
|
|
// Assume any path segment starting with a digit is an ID, and replace
|
|
// it with `:id`. This will help group related routes in Sentry stats.
|
|
// NOTE: I'm a bit uncertain about the timing on this for tracking
|
|
// client-side navs... but we now only track first-time
|
|
// pageloads, and it definitely works correctly for them!
|
|
name: window.location.pathname.replaceAll(/\/[0-9][^/]*/g, "/:id"),
|
|
}),
|
|
|
|
// We have a _lot_ of location changes that don't actually signify useful
|
|
// navigations, like in the wardrobe page. It could be useful to trace
|
|
// them with better filtering someday, but frankly we don't use the perf
|
|
// features besides Web Vitals right now, and those only get tracked on
|
|
// first-time pageloads, anyway. So, don't track client-side navs!
|
|
startTransactionOnLocationChange: false,
|
|
}),
|
|
],
|
|
denyUrls: [
|
|
// Don't log errors that were probably triggered by extensions and not by
|
|
// our own app. (Apparently Sentry's setting to ignore browser extension
|
|
// errors doesn't do this anywhere near as consistently as I'd expect?)
|
|
//
|
|
// Adapted from https://gist.github.com/impressiver/5092952, as linked in
|
|
// https://docs.sentry.io/platforms/javascript/configuration/filtering/.
|
|
/^chrome-extension:\/\//,
|
|
/^moz-extension:\/\//,
|
|
],
|
|
|
|
// Since we're only tracking first-page loads and not navigations, 100%
|
|
// sampling isn't actually so much! Tune down if it becomes a problem, tho.
|
|
tracesSampleRate: 1.0,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* ScopedCSSReset applies a copy of Chakra UI's CSS reset, but only to its
|
|
* children (or, well, any element with the chakra-css-reset class). It also
|
|
* applies to .chakra-portal elements mounted by <Portal> components.
|
|
*
|
|
* We also apply some base styles, like the default text color.
|
|
*
|
|
* NOTE: We use the `:where` CSS selector, instead of the .chakra-css-reset
|
|
* selector directly, to avoid specificity conflicts. e.g. the selector
|
|
* `.chakra-css-reset h1` is considered MORE specific than `.my-h1`, whereas
|
|
* the selector `:where(.chakra-css-reset) h1` is lower specificity.
|
|
*/
|
|
function ScopedCSSReset({ children }) {
|
|
// Get the current theme and color mode.
|
|
//
|
|
// NOTE: The theme object returned by `useTheme` has some extensions that are
|
|
// necessary for the code below, but aren't present in the theme config
|
|
// returned by `extendTheme`! That's why we use this here instead of `theme`.
|
|
const liveTheme = useTheme();
|
|
const colorMode = useColorMode();
|
|
|
|
// Resolve the theme's global styles into CSS objects for Emotion.
|
|
const globalStylesCSS = resolveCSS(
|
|
globalStyles({ theme: liveTheme, colorMode }),
|
|
)(liveTheme);
|
|
|
|
// Prepend our special scope selector to the global styles.
|
|
const scopedGlobalStylesCSS = {};
|
|
for (let [selector, rules] of Object.entries(globalStylesCSS)) {
|
|
// The `body` selector is where typography etc rules go, but `body` isn't
|
|
// actually *inside* our scoped element! Instead, ignore the `body` part,
|
|
// and just apply it to the scoping element itself.
|
|
if (selector.trim() === "body") {
|
|
selector = "";
|
|
}
|
|
|
|
const scopedSelector =
|
|
":where(.chakra-css-reset, .chakra-portal) " + selector;
|
|
scopedGlobalStylesCSS[scopedSelector] = rules;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Box className="chakra-css-reset">{children}</Box>
|
|
<Global
|
|
styles={css`
|
|
/* Chakra's default global styles, placed here so we can override
|
|
* the actual _global_ styles in the theme to be empty. That way,
|
|
* it only affects Chakra stuff, not all elements! */
|
|
${scopedGlobalStylesCSS}
|
|
|
|
/* Chakra's CSS reset, copy-pasted and rescoped! */
|
|
:where(.chakra-css-reset, .chakra-portal) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
border-width: 0;
|
|
border-style: solid;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
main {
|
|
display: block;
|
|
}
|
|
|
|
hr {
|
|
border-top-width: 1px;
|
|
box-sizing: content-box;
|
|
height: 0;
|
|
overflow: visible;
|
|
}
|
|
|
|
pre,
|
|
code,
|
|
kbd,
|
|
samp {
|
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
font-size: 1em;
|
|
}
|
|
|
|
a {
|
|
background-color: transparent;
|
|
color: inherit;
|
|
text-decoration: inherit;
|
|
}
|
|
|
|
abbr[title] {
|
|
border-bottom: none;
|
|
text-decoration: underline;
|
|
-webkit-text-decoration: underline dotted;
|
|
text-decoration: underline dotted;
|
|
}
|
|
|
|
b,
|
|
strong {
|
|
font-weight: bold;
|
|
}
|
|
|
|
small {
|
|
font-size: 80%;
|
|
}
|
|
|
|
sub,
|
|
sup {
|
|
font-size: 75%;
|
|
line-height: 0;
|
|
position: relative;
|
|
vertical-align: baseline;
|
|
}
|
|
|
|
sub {
|
|
bottom: -0.25em;
|
|
}
|
|
|
|
sup {
|
|
top: -0.5em;
|
|
}
|
|
|
|
img {
|
|
border-style: none;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
optgroup,
|
|
select,
|
|
textarea {
|
|
font-family: inherit;
|
|
font-size: 100%;
|
|
line-height: 1.15;
|
|
margin: 0;
|
|
}
|
|
|
|
button,
|
|
input {
|
|
overflow: visible;
|
|
}
|
|
|
|
button,
|
|
select {
|
|
text-transform: none;
|
|
}
|
|
|
|
button::-moz-focus-inner,
|
|
[type="button"]::-moz-focus-inner,
|
|
[type="reset"]::-moz-focus-inner,
|
|
[type="submit"]::-moz-focus-inner {
|
|
border-style: none;
|
|
padding: 0;
|
|
}
|
|
|
|
fieldset {
|
|
padding: 0.35em 0.75em 0.625em;
|
|
}
|
|
|
|
legend {
|
|
box-sizing: border-box;
|
|
color: inherit;
|
|
display: table;
|
|
max-width: 100%;
|
|
padding: 0;
|
|
white-space: normal;
|
|
}
|
|
|
|
progress {
|
|
vertical-align: baseline;
|
|
}
|
|
|
|
textarea {
|
|
overflow: auto;
|
|
}
|
|
|
|
[type="checkbox"],
|
|
[type="radio"] {
|
|
box-sizing: border-box;
|
|
padding: 0;
|
|
}
|
|
|
|
[type="number"]::-webkit-inner-spin-button,
|
|
[type="number"]::-webkit-outer-spin-button {
|
|
-webkit-appearance: none !important;
|
|
}
|
|
|
|
input[type="number"] {
|
|
-moz-appearance: textfield;
|
|
}
|
|
|
|
[type="search"] {
|
|
-webkit-appearance: textfield;
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
[type="search"]::-webkit-search-decoration {
|
|
-webkit-appearance: none !important;
|
|
}
|
|
|
|
::-webkit-file-upload-button {
|
|
-webkit-appearance: button;
|
|
font: inherit;
|
|
}
|
|
|
|
details {
|
|
display: block;
|
|
}
|
|
|
|
summary {
|
|
display: list-item;
|
|
}
|
|
|
|
template {
|
|
display: none;
|
|
}
|
|
|
|
[hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
body,
|
|
blockquote,
|
|
dl,
|
|
dd,
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6,
|
|
hr,
|
|
figure,
|
|
p,
|
|
pre {
|
|
margin: 0;
|
|
}
|
|
|
|
button {
|
|
background: transparent;
|
|
padding: 0;
|
|
}
|
|
|
|
fieldset {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
ol,
|
|
ul {
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
textarea {
|
|
resize: vertical;
|
|
}
|
|
|
|
button,
|
|
[role="button"] {
|
|
cursor: pointer;
|
|
}
|
|
|
|
button::-moz-focus-inner {
|
|
border: 0 !important;
|
|
}
|
|
|
|
table {
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6 {
|
|
font-size: inherit;
|
|
font-weight: inherit;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
optgroup,
|
|
select,
|
|
textarea {
|
|
padding: 0;
|
|
line-height: inherit;
|
|
color: inherit;
|
|
}
|
|
|
|
img,
|
|
svg,
|
|
video,
|
|
canvas,
|
|
audio,
|
|
iframe,
|
|
embed,
|
|
object {
|
|
display: block;
|
|
}
|
|
|
|
img,
|
|
video {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
[data-js-focus-visible] :focus:not([data-focus-visible-added]) {
|
|
outline: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
select::-ms-expand {
|
|
display: none;
|
|
}
|
|
}
|
|
`}
|
|
/>
|
|
</>
|
|
);
|
|
}
|