## Summary - Fixes periodic production crashes (undici ECONNREFUSED ::1) by bounding server cache size/lifetime and hardening server HTTP client. ### Root cause - React server cache (cache(...)) held axios responses indefinitely across many parameter combinations, causing slow memory growth until the Next.js app router worker was OOM-killed. The main server then failed IPC to the worker (ECONNREFUSED ::1:<port>). ### Changes - `app/lib/data.ts`: Replace unbounded cache(...) with unstable_cache and explicit keys; TTLs: 60s for teams/detail/favorites/user, 300s for meta (jobs/skills/accessories/raids/version). - `app/lib/api-utils.ts`: Add shared Axios instance with 15s timeout and keepAlive http/https agents; apply to GET/POST/PUT/DELETE helpers. - `pages/api/preview/[shortcode].ts`: Remove duplicate handler to dedupe route; retain the .tsx variant using `NEXT_PUBLIC_SIERO_API_URL`. ### Notes - Build currently has pre-existing app/pages route duplication errors; out of scope here but unrelated to this fix. - Ensure `NEXT_PUBLIC_SIERO_API_URL` and `NEXT_PUBLIC_SIERO_OAUTH_URL` are set on Railway. ### Risk/impact - Low risk; behavior is unchanged aside from bounded caching and resilient HTTP. - Cache TTLs can be tuned later if needed. ### Test plan - Verify saved/teams/user pages load and revalidate after TTL. - Validate API routes still proxy correctly; timeouts occur after ~15s for hung upstreams. - Monitor memory over several days; expect stable usage without steady growth.
411 lines
6.6 KiB
SCSS
411 lines
6.6 KiB
SCSS
@import '~meyer-reset-scss';
|
|
@import 'themes.scss';
|
|
@import '../app/styles/error.scss';
|
|
|
|
html {
|
|
background-color: var(--background);
|
|
font-size: 62.5%;
|
|
// height: 100%;
|
|
}
|
|
|
|
body {
|
|
-webkit-font-smoothing: antialiased;
|
|
box-sizing: border-box;
|
|
font-family: var(--font-family);
|
|
font-size: 1.4rem;
|
|
height: 100%;
|
|
padding: $unit-2x !important;
|
|
|
|
&.no-scroll {
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
|
|
#__next {
|
|
height: 100%;
|
|
}
|
|
|
|
main {
|
|
font-family: var(--font-goalking);
|
|
min-height: 90%;
|
|
}
|
|
|
|
a {
|
|
color: $blue;
|
|
text-decoration: none;
|
|
|
|
&:visited {
|
|
color: $blue;
|
|
}
|
|
|
|
&.wind {
|
|
color: var(--wind-bg);
|
|
}
|
|
|
|
&.fire {
|
|
color: var(--fire-bg);
|
|
}
|
|
|
|
&.water {
|
|
color: var(--water-bg);
|
|
}
|
|
|
|
&.earth {
|
|
color: var(--earth-bg);
|
|
}
|
|
|
|
&.dark {
|
|
color: var(--dark-bg);
|
|
}
|
|
|
|
&.light {
|
|
color: var(--light-bg);
|
|
}
|
|
}
|
|
|
|
button,
|
|
input,
|
|
textarea {
|
|
border: 2px solid transparent;
|
|
font-family: var(--font-family);
|
|
font-size: $font-regular;
|
|
}
|
|
|
|
button:focus-visible {
|
|
border: 2px solid $blue;
|
|
outline: none;
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
p {
|
|
color: var(--text-primary);
|
|
line-height: 1.3;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.1rem;
|
|
font-weight: $medium;
|
|
text-align: center;
|
|
}
|
|
|
|
h5 {
|
|
font-size: $font-small;
|
|
font-weight: $medium;
|
|
// opacity: 0.7;
|
|
|
|
&.wind {
|
|
color: var(--wind-text);
|
|
}
|
|
|
|
&.fire {
|
|
color: var(--fire-text);
|
|
}
|
|
|
|
&.water {
|
|
color: var(--water-text);
|
|
}
|
|
|
|
&.earth {
|
|
color: var(--earth-text);
|
|
}
|
|
|
|
&.dark {
|
|
color: var(--dark-text);
|
|
}
|
|
|
|
&.light {
|
|
color: var(--light-text);
|
|
}
|
|
}
|
|
|
|
// Used for collection pages
|
|
.teams,
|
|
.profile {
|
|
display: flex;
|
|
height: 100%;
|
|
flex-direction: column;
|
|
gap: $unit * 2;
|
|
|
|
h1 {
|
|
font-weight: $medium;
|
|
}
|
|
}
|
|
|
|
// Used for static pages
|
|
.PageContent {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $unit-4x;
|
|
max-width: $grid-width;
|
|
margin: $unit-4x auto 0;
|
|
|
|
h1 {
|
|
font-size: $font-xxlarge;
|
|
text-align: left;
|
|
}
|
|
|
|
h2 {
|
|
font-size: $font-medium;
|
|
font-weight: $medium;
|
|
margin-bottom: $unit * 3;
|
|
}
|
|
|
|
p,
|
|
li {
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
img.profile {
|
|
background: $grey-90;
|
|
|
|
&.fire {
|
|
background: $fire-bg-20;
|
|
}
|
|
|
|
&.water {
|
|
background: $water-bg-20;
|
|
}
|
|
|
|
&.wind {
|
|
background: $wind-bg-20;
|
|
}
|
|
|
|
&.earth {
|
|
background: $earth-bg-20;
|
|
}
|
|
|
|
&.dark {
|
|
background: $dark-bg-10;
|
|
}
|
|
|
|
&.light {
|
|
background: $light-bg-20;
|
|
}
|
|
|
|
&.anonymous {
|
|
background: var(--anonymous-bg);
|
|
}
|
|
}
|
|
|
|
.infinite-scroll-component {
|
|
overflow: hidden !important;
|
|
}
|
|
|
|
.SearchFilterBar {
|
|
display: flex;
|
|
gap: $unit;
|
|
padding: 0 ($unit * 3);
|
|
|
|
@include breakpoint(phone) {
|
|
display: grid;
|
|
gap: 8px;
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
|
|
.Joined {
|
|
$offset: 2px;
|
|
|
|
align-items: center;
|
|
background: var(--input-bg);
|
|
border-radius: $input-corner;
|
|
border: $offset solid transparent;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
gap: $unit;
|
|
padding-top: 2px;
|
|
padding-bottom: 2px;
|
|
padding-right: calc($unit-2x - $offset);
|
|
|
|
&.Bound {
|
|
background-color: var(--input-bound-bg);
|
|
|
|
&:hover {
|
|
background-color: var(--input-bound-bg-hover);
|
|
}
|
|
}
|
|
|
|
&:focus-within {
|
|
border: $offset solid $blue;
|
|
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
|
}
|
|
|
|
.Counter {
|
|
color: $grey-55;
|
|
font-weight: $bold;
|
|
line-height: 42px;
|
|
}
|
|
|
|
.Input {
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 0;
|
|
padding: $unit * 1.5 $unit-2x;
|
|
padding-left: calc($unit-2x - $offset);
|
|
|
|
&:focus {
|
|
border: none;
|
|
outline: none;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used for server unavailable component
|
|
.ServerUnavailableWrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
|
|
.ServerUnavailable {
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $unit-6x;
|
|
margin: auto;
|
|
max-width: 440px;
|
|
|
|
.Message {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: $unit;
|
|
|
|
h1 {
|
|
color: var(--text-primary);
|
|
font-size: $font-xxlarge;
|
|
font-weight: $bold;
|
|
text-align: center;
|
|
}
|
|
|
|
p {
|
|
color: var(--text-secondary);
|
|
font-size: $font-regular;
|
|
font-weight: $bold;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.Connect {
|
|
color: var(--text-tertiary);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: $unit-2x;
|
|
width: 100%;
|
|
|
|
p {
|
|
text-align: center;
|
|
}
|
|
|
|
.LinkItem {
|
|
$diameter: $unit-6x;
|
|
align-items: center;
|
|
background: var(--dialog-bg);
|
|
border: 1px solid var(--link-item-bg);
|
|
border-radius: $card-corner;
|
|
display: flex;
|
|
min-height: 82px;
|
|
transition: background $duration-zoom ease-in,
|
|
transform $duration-zoom ease-in;
|
|
|
|
&:hover {
|
|
background: var(--link-item-bg);
|
|
color: var(--text-primary);
|
|
|
|
.shareIcon {
|
|
fill: var(--text-primary);
|
|
transform: translate($unit-half, calc(($unit * -1) / 2));
|
|
}
|
|
}
|
|
|
|
.Left {
|
|
align-items: center;
|
|
display: flex;
|
|
gap: $unit;
|
|
|
|
h3 {
|
|
font-weight: 600;
|
|
max-width: 70%;
|
|
line-height: 1.3;
|
|
}
|
|
}
|
|
|
|
a {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: $unit-2x;
|
|
width: 100%;
|
|
|
|
&:hover {
|
|
text-decoration: none;
|
|
}
|
|
|
|
.left {
|
|
align-items: center;
|
|
display: flex;
|
|
gap: $unit-2x;
|
|
flex-grow: 1;
|
|
|
|
h3 {
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
}
|
|
|
|
svg {
|
|
fill: var(--link-item-image-color);
|
|
width: $diameter;
|
|
height: auto;
|
|
transition: fill $duration-zoom ease-in;
|
|
|
|
&.shareIcon {
|
|
width: $unit-4x;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used when a resource can be found
|
|
.notFound {
|
|
height: 200px;
|
|
width: 400px;
|
|
margin: auto;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
|
|
h2 {
|
|
color: $grey-60;
|
|
font-size: $font-regular;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
// Used in _app.tsx
|
|
.ToastViewport {
|
|
position: fixed;
|
|
bottom: 0px;
|
|
right: 0px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 340px;
|
|
max-width: 100vw;
|
|
z-index: 2147483647;
|
|
padding: 25px;
|
|
gap: 10px;
|
|
margin: 0px;
|
|
list-style: none;
|
|
outline: none;
|
|
}
|
|
|
|
div[data-radix-popper-content-wrapper] {
|
|
z-index: 100 !important;
|
|
}
|