diff --git a/app/core/components/spinner.module.css b/app/core/components/spinner.module.css new file mode 100644 index 0000000..2bc3648 --- /dev/null +++ b/app/core/components/spinner.module.css @@ -0,0 +1,15 @@ +.ring { + display: inline-block; + width: 50px; + height: 50px; + border: 3px solid rgba(0, 0, 0, 0.15); + border-radius: 50%; + border-top-color: currentColor; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/app/core/components/spinner.tsx b/app/core/components/spinner.tsx new file mode 100644 index 0000000..a53049f --- /dev/null +++ b/app/core/components/spinner.tsx @@ -0,0 +1,11 @@ +import clsx from "clsx"; + +import styles from "./spinner.module.css"; + +export default function Spinner() { + return ( +
+
+
+ ); +} diff --git a/app/core/layouts/layout/footer.tsx b/app/core/layouts/layout/footer.tsx index b2ce2ed..b774e91 100644 --- a/app/core/layouts/layout/footer.tsx +++ b/app/core/layouts/layout/footer.tsx @@ -29,7 +29,7 @@ function NavLink({ path, label, icon }: NavLinkProps) { return (
- + = ({ children, title, pageTitle = title, hideFooter = false }) => { +const AppLayout: FunctionComponent = ({ children, title, pageTitle = title, hideFooter = false }) => { return ( <> {pageTitle ? ( @@ -32,16 +33,18 @@ const Layout: FunctionComponent = ({ children, title, pageTitle = title, ) : null} -
-
-
-
- {children} -
+ }> +
+
+
+
+ {children} +
+
+ {!hideFooter ?
: null}
- {!hideFooter ?
: null}
-
+ ); }; @@ -109,4 +112,4 @@ const ErrorBoundary = withRouter( }, ); -export default Layout; +export default AppLayout; diff --git a/app/core/layouts/layout/loader-gradient.js b/app/core/layouts/layout/loader-gradient.js new file mode 100644 index 0000000..c2e4344 --- /dev/null +++ b/app/core/layouts/layout/loader-gradient.js @@ -0,0 +1,749 @@ +/* + * Stripe WebGl Gradient Animation + * All Credits to Stripe.com + * ScrollObserver functionality to disable animation when not scrolled into view has been disabled and + * commented out for now. + * https://kevinhufnagl.com + */ + +//Converting colors to proper format +function normalizeColor(hexCode) { + return [((hexCode >> 16) & 255) / 255, ((hexCode >> 8) & 255) / 255, (255 & hexCode) / 255]; +} +["SCREEN", "LINEAR_LIGHT"].reduce( + (hexCode, t, n) => + Object.assign(hexCode, { + [t]: n, + }), + {}, +); + +//Essential functionality of WebGl +//t = width +//n = height +class MiniGl { + constructor(canvas, width, height, debug = false) { + const _miniGl = this, + debug_output = -1 !== document.location.search.toLowerCase().indexOf("debug=webgl"); + (_miniGl.canvas = canvas), + (_miniGl.gl = _miniGl.canvas.getContext("webgl", { + antialias: true, + })), + (_miniGl.meshes = []); + const context = _miniGl.gl; + width && height && this.setSize(width, height), + _miniGl.lastDebugMsg, + (_miniGl.debug = + debug && debug_output + ? function (e) { + const t = new Date(); + t - _miniGl.lastDebugMsg > 1e3 && console.log("---"), + console.log( + t.toLocaleTimeString() + Array(Math.max(0, 32 - e.length)).join(" ") + e + ": ", + ...Array.from(arguments).slice(1), + ), + (_miniGl.lastDebugMsg = t); + } + : () => {}), + Object.defineProperties(_miniGl, { + Material: { + enumerable: false, + value: class { + constructor(vertexShaders, fragments, uniforms = {}) { + const material = this; + function getShaderByType(type, source) { + const shader = context.createShader(type); + return ( + context.shaderSource(shader, source), + context.compileShader(shader), + context.getShaderParameter(shader, context.COMPILE_STATUS) || + console.error(context.getShaderInfoLog(shader)), + _miniGl.debug("Material.compileShaderSource", { + source: source, + }), + shader + ); + } + function getUniformVariableDeclarations(uniforms, type) { + return Object.entries(uniforms) + .map(([uniform, value]) => value.getDeclaration(uniform, type)) + .join("\n"); + } + (material.uniforms = uniforms), (material.uniformInstances = []); + + const prefix = "\n precision highp float;\n "; + (material.vertexSource = `\n ${prefix}\n attribute vec4 position;\n attribute vec2 uv;\n attribute vec2 uvNorm;\n ${getUniformVariableDeclarations( + _miniGl.commonUniforms, + "vertex", + )}\n ${getUniformVariableDeclarations( + uniforms, + "vertex", + )}\n ${vertexShaders}\n `), + (material.Source = `\n ${prefix}\n ${getUniformVariableDeclarations( + _miniGl.commonUniforms, + "fragment", + )}\n ${getUniformVariableDeclarations( + uniforms, + "fragment", + )}\n ${fragments}\n `), + (material.vertexShader = getShaderByType(context.VERTEX_SHADER, material.vertexSource)), + (material.fragmentShader = getShaderByType(context.FRAGMENT_SHADER, material.Source)), + (material.program = context.createProgram()), + context.attachShader(material.program, material.vertexShader), + context.attachShader(material.program, material.fragmentShader), + context.linkProgram(material.program), + context.getProgramParameter(material.program, context.LINK_STATUS) || + console.error(context.getProgramInfoLog(material.program)), + context.useProgram(material.program), + material.attachUniforms(void 0, _miniGl.commonUniforms), + material.attachUniforms(void 0, material.uniforms); + } + //t = uniform + attachUniforms(name, uniforms) { + //n = material + const material = this; + void 0 === name + ? Object.entries(uniforms).forEach(([name, uniform]) => { + material.attachUniforms(name, uniform); + }) + : "array" == uniforms.type + ? uniforms.value.forEach((uniform, i) => + material.attachUniforms(`${name}[${i}]`, uniform), + ) + : "struct" == uniforms.type + ? Object.entries(uniforms.value).forEach(([uniform, i]) => + material.attachUniforms(`${name}.${uniform}`, i), + ) + : (_miniGl.debug("Material.attachUniforms", { + name: name, + uniform: uniforms, + }), + material.uniformInstances.push({ + uniform: uniforms, + location: context.getUniformLocation(material.program, name), + })); + } + }, + }, + Uniform: { + enumerable: !1, + value: class { + constructor(e) { + (this.type = "float"), Object.assign(this, e); + (this.typeFn = + { + float: "1f", + int: "1i", + vec2: "2fv", + vec3: "3fv", + vec4: "4fv", + mat4: "Matrix4fv", + }[this.type] || "1f"), + this.update(); + } + update(value) { + void 0 !== this.value && + context[`uniform${this.typeFn}`]( + value, + 0 === this.typeFn.indexOf("Matrix") ? this.transpose : this.value, + 0 === this.typeFn.indexOf("Matrix") ? this.value : null, + ); + } + //e - name + //t - type + //n - length + getDeclaration(name, type, length) { + const uniform = this; + if (uniform.excludeFrom !== type) { + if ("array" === uniform.type) + return ( + uniform.value[0].getDeclaration(name, type, uniform.value.length) + + `\nconst int ${name}_length = ${uniform.value.length};` + ); + if ("struct" === uniform.type) { + let name_no_prefix = name.replace("u_", ""); + return ( + (name_no_prefix = + name_no_prefix.charAt(0).toUpperCase() + name_no_prefix.slice(1)), + `uniform struct ${name_no_prefix} + {\n` + + Object.entries(uniform.value) + .map(([name, uniform]) => + uniform.getDeclaration(name, type).replace(/^uniform/, ""), + ) + .join("") + + `\n} ${name}${length > 0 ? `[${length}]` : ""};` + ); + } + return `uniform ${uniform.type} ${name}${length > 0 ? `[${length}]` : ""};`; + } + } + }, + }, + PlaneGeometry: { + enumerable: !1, + value: class { + constructor(width, height, n, i, orientation) { + context.createBuffer(), + (this.attributes = { + position: new _miniGl.Attribute({ + target: context.ARRAY_BUFFER, + size: 3, + }), + uv: new _miniGl.Attribute({ + target: context.ARRAY_BUFFER, + size: 2, + }), + uvNorm: new _miniGl.Attribute({ + target: context.ARRAY_BUFFER, + size: 2, + }), + index: new _miniGl.Attribute({ + target: context.ELEMENT_ARRAY_BUFFER, + size: 3, + type: context.UNSIGNED_SHORT, + }), + }), + this.setTopology(n, i), + this.setSize(width, height, orientation); + } + setTopology(e = 1, t = 1) { + const n = this; + (n.xSegCount = e), + (n.ySegCount = t), + (n.vertexCount = (n.xSegCount + 1) * (n.ySegCount + 1)), + (n.quadCount = n.xSegCount * n.ySegCount * 2), + (n.attributes.uv.values = new Float32Array(2 * n.vertexCount)), + (n.attributes.uvNorm.values = new Float32Array(2 * n.vertexCount)), + (n.attributes.index.values = new Uint16Array(3 * n.quadCount)); + for (let e = 0; e <= n.ySegCount; e++) + for (let t = 0; t <= n.xSegCount; t++) { + const i = e * (n.xSegCount + 1) + t; + if ( + ((n.attributes.uv.values[2 * i] = t / n.xSegCount), + (n.attributes.uv.values[2 * i + 1] = 1 - e / n.ySegCount), + (n.attributes.uvNorm.values[2 * i] = (t / n.xSegCount) * 2 - 1), + (n.attributes.uvNorm.values[2 * i + 1] = 1 - (e / n.ySegCount) * 2), + t < n.xSegCount && e < n.ySegCount) + ) { + const s = e * n.xSegCount + t; + (n.attributes.index.values[6 * s] = i), + (n.attributes.index.values[6 * s + 1] = i + 1 + n.xSegCount), + (n.attributes.index.values[6 * s + 2] = i + 1), + (n.attributes.index.values[6 * s + 3] = i + 1), + (n.attributes.index.values[6 * s + 4] = i + 1 + n.xSegCount), + (n.attributes.index.values[6 * s + 5] = i + 2 + n.xSegCount); + } + } + n.attributes.uv.update(), + n.attributes.uvNorm.update(), + n.attributes.index.update(), + _miniGl.debug("Geometry.setTopology", { + uv: n.attributes.uv, + uvNorm: n.attributes.uvNorm, + index: n.attributes.index, + }); + } + setSize(width = 1, height = 1, orientation = "xz") { + const geometry = this; + (geometry.width = width), + (geometry.height = height), + (geometry.orientation = orientation), + (geometry.attributes.position.values && + geometry.attributes.position.values.length === 3 * geometry.vertexCount) || + (geometry.attributes.position.values = new Float32Array(3 * geometry.vertexCount)); + const o = width / -2, + r = height / -2, + segment_width = width / geometry.xSegCount, + segment_height = height / geometry.ySegCount; + for (let yIndex = 0; yIndex <= geometry.ySegCount; yIndex++) { + const t = r + yIndex * segment_height; + for (let xIndex = 0; xIndex <= geometry.xSegCount; xIndex++) { + const r = o + xIndex * segment_width, + l = yIndex * (geometry.xSegCount + 1) + xIndex; + (geometry.attributes.position.values[3 * l + "xyz".indexOf(orientation[0])] = r), + (geometry.attributes.position.values[3 * l + "xyz".indexOf(orientation[1])] = + -t); + } + } + geometry.attributes.position.update(), + _miniGl.debug("Geometry.setSize", { + position: geometry.attributes.position, + }); + } + }, + }, + Mesh: { + enumerable: !1, + value: class { + constructor(geometry, material) { + const mesh = this; + (mesh.geometry = geometry), + (mesh.material = material), + (mesh.wireframe = !1), + (mesh.attributeInstances = []), + Object.entries(mesh.geometry.attributes).forEach(([e, attribute]) => { + mesh.attributeInstances.push({ + attribute: attribute, + location: attribute.attach(e, mesh.material.program), + }); + }), + _miniGl.meshes.push(mesh), + _miniGl.debug("Mesh.constructor", { + mesh: mesh, + }); + } + draw() { + context.useProgram(this.material.program), + this.material.uniformInstances.forEach(({ uniform: e, location: t }) => e.update(t)), + this.attributeInstances.forEach(({ attribute: e, location: t }) => e.use(t)), + context.drawElements( + this.wireframe ? context.LINES : context.TRIANGLES, + this.geometry.attributes.index.values.length, + context.UNSIGNED_SHORT, + 0, + ); + } + remove() { + _miniGl.meshes = _miniGl.meshes.filter((e) => e != this); + } + }, + }, + Attribute: { + enumerable: !1, + value: class { + constructor(e) { + (this.type = context.FLOAT), + (this.normalized = !1), + (this.buffer = context.createBuffer()), + Object.assign(this, e), + this.update(); + } + update() { + void 0 !== this.values && + (context.bindBuffer(this.target, this.buffer), + context.bufferData(this.target, this.values, context.STATIC_DRAW)); + } + attach(e, t) { + const n = context.getAttribLocation(t, e); + return ( + this.target === context.ARRAY_BUFFER && + (context.enableVertexAttribArray(n), + context.vertexAttribPointer(n, this.size, this.type, this.normalized, 0, 0)), + n + ); + } + use(e) { + context.bindBuffer(this.target, this.buffer), + this.target === context.ARRAY_BUFFER && + (context.enableVertexAttribArray(e), + context.vertexAttribPointer(e, this.size, this.type, this.normalized, 0, 0)); + } + }, + }, + }); + const a = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + _miniGl.commonUniforms = { + projectionMatrix: new _miniGl.Uniform({ + type: "mat4", + value: a, + }), + modelViewMatrix: new _miniGl.Uniform({ + type: "mat4", + value: a, + }), + resolution: new _miniGl.Uniform({ + type: "vec2", + value: [1, 1], + }), + aspectRatio: new _miniGl.Uniform({ + type: "float", + value: 1, + }), + }; + } + setSize(e = 640, t = 480) { + (this.width = e), + (this.height = t), + (this.canvas.width = e), + (this.canvas.height = t), + this.gl.viewport(0, 0, e, t), + (this.commonUniforms.resolution.value = [e, t]), + (this.commonUniforms.aspectRatio.value = e / t), + this.debug("MiniGL.setSize", { + width: e, + height: t, + }); + } + //left, right, top, bottom, near, far + setOrthographicCamera(e = 0, t = 0, n = 0, i = -2e3, s = 2e3) { + (this.commonUniforms.projectionMatrix.value = [ + 2 / this.width, + 0, + 0, + 0, + 0, + 2 / this.height, + 0, + 0, + 0, + 0, + 2 / (i - s), + 0, + e, + t, + n, + 1, + ]), + this.debug("setOrthographicCamera", this.commonUniforms.projectionMatrix.value); + } + render() { + this.gl.clearColor(0, 0, 0, 0), this.gl.clearDepth(1), this.meshes.forEach((e) => e.draw()); + } +} + +//Sets initial properties +function e(object, propertyName, val) { + return ( + propertyName in object + ? Object.defineProperty(object, propertyName, { + value: val, + enumerable: !0, + configurable: !0, + writable: !0, + }) + : (object[propertyName] = val), + object + ); +} + +//Gradient object +class Gradient { + constructor(...t) { + e(this, "el", void 0), + e(this, "cssVarRetries", 0), + e(this, "maxCssVarRetries", 200), + e(this, "angle", 0), + e(this, "isLoadedClass", !1), + e(this, "isScrolling", !1), + /*e(this, "isStatic", o.disableAmbientAnimations()),*/ e(this, "scrollingTimeout", void 0), + e(this, "scrollingRefreshDelay", 200), + e(this, "isIntersecting", !1), + e(this, "shaderFiles", void 0), + e(this, "vertexShader", void 0), + e(this, "sectionColors", void 0), + e(this, "computedCanvasStyle", void 0), + e(this, "conf", void 0), + e(this, "uniforms", void 0), + e(this, "t", 1253106), + e(this, "last", 0), + e(this, "width", void 0), + e(this, "minWidth", 1111), + e(this, "height", 600), + e(this, "xSegCount", void 0), + e(this, "ySegCount", void 0), + e(this, "mesh", void 0), + e(this, "material", void 0), + e(this, "geometry", void 0), + e(this, "minigl", void 0), + e(this, "scrollObserver", void 0), + e(this, "amp", 320), + e(this, "seed", 5), + e(this, "freqX", 14e-5), + e(this, "freqY", 29e-5), + e(this, "freqDelta", 1e-5), + e(this, "activeColors", [1, 1, 1, 1]), + e(this, "isMetaKey", !1), + e(this, "isGradientLegendVisible", !1), + e(this, "isMouseDown", !1), + e(this, "handleScroll", () => { + clearTimeout(this.scrollingTimeout), + (this.scrollingTimeout = setTimeout(this.handleScrollEnd, this.scrollingRefreshDelay)), + this.isGradientLegendVisible && this.hideGradientLegend(), + this.conf.playing && ((this.isScrolling = !0), this.pause()); + }), + e(this, "handleScrollEnd", () => { + (this.isScrolling = !1), this.isIntersecting && this.play(); + }), + e(this, "resize", () => { + (this.width = window.innerWidth), + this.minigl.setSize(this.width, this.height), + this.minigl.setOrthographicCamera(), + (this.xSegCount = Math.ceil(this.width * this.conf.density[0])), + (this.ySegCount = Math.ceil(this.height * this.conf.density[1])), + this.mesh.geometry.setTopology(this.xSegCount, this.ySegCount), + this.mesh.geometry.setSize(this.width, this.height), + (this.mesh.material.uniforms.u_shadow_power.value = this.width < 600 ? 5 : 6); + }), + e(this, "handleMouseDown", (e) => { + this.isGradientLegendVisible && + ((this.isMetaKey = e.metaKey), + (this.isMouseDown = !0), + !1 === this.conf.playing && requestAnimationFrame(this.animate)); + }), + e(this, "handleMouseUp", () => { + this.isMouseDown = !1; + }), + e(this, "animate", (e) => { + if (!this.shouldSkipFrame(e) || this.isMouseDown) { + if (((this.t += Math.min(e - this.last, 1e3 / 15)), (this.last = e), this.isMouseDown)) { + let e = 160; + this.isMetaKey && (e = -160), (this.t += e); + } + (this.mesh.material.uniforms.u_time.value = this.t), this.minigl.render(); + } + if (0 !== this.last && this.isStatic) return this.minigl.render(), void this.disconnect(); + /*this.isIntersecting && */ (this.conf.playing || this.isMouseDown) && + requestAnimationFrame(this.animate); + }), + e(this, "addIsLoadedClass", () => { + /*this.isIntersecting && */ !this.isLoadedClass && + ((this.isLoadedClass = !0), + this.el.classList.add("isLoaded"), + setTimeout(() => { + this.el.parentElement.classList.add("isLoaded"); + }, 3e3)); + }), + e(this, "pause", () => { + this.conf.playing = false; + }), + e(this, "play", () => { + requestAnimationFrame(this.animate), (this.conf.playing = true); + }), + e(this, "initGradient", (selector) => { + this.el = document.querySelector(selector); + this.connect(); + return this; + }); + } + async connect() { + (this.shaderFiles = { + vertex: "varying vec3 v_color;\n\nvoid main() {\n float time = u_time * u_global.noiseSpeed;\n\n vec2 noiseCoord = resolution * uvNorm * u_global.noiseFreq;\n\n vec2 st = 1. - uvNorm.xy;\n\n //\n // Tilting the plane\n //\n\n // Front-to-back tilt\n float tilt = resolution.y / 2.0 * uvNorm.y;\n\n // Left-to-right angle\n float incline = resolution.x * uvNorm.x / 2.0 * u_vertDeform.incline;\n\n // Up-down shift to offset incline\n float offset = resolution.x / 2.0 * u_vertDeform.incline * mix(u_vertDeform.offsetBottom, u_vertDeform.offsetTop, uv.y);\n\n //\n // Vertex noise\n //\n\n float noise = snoise(vec3(\n noiseCoord.x * u_vertDeform.noiseFreq.x + time * u_vertDeform.noiseFlow,\n noiseCoord.y * u_vertDeform.noiseFreq.y,\n time * u_vertDeform.noiseSpeed + u_vertDeform.noiseSeed\n )) * u_vertDeform.noiseAmp;\n\n // Fade noise to zero at edges\n noise *= 1.0 - pow(abs(uvNorm.y), 2.0);\n\n // Clamp to 0\n noise = max(0.0, noise);\n\n vec3 pos = vec3(\n position.x,\n position.y + tilt + incline + noise - offset,\n position.z\n );\n\n //\n // Vertex color, to be passed to fragment shader\n //\n\n if (u_active_colors[0] == 1.) {\n v_color = u_baseColor;\n }\n\n for (int i = 0; i < u_waveLayers_length; i++) {\n if (u_active_colors[i + 1] == 1.) {\n WaveLayers layer = u_waveLayers[i];\n\n float noise = smoothstep(\n layer.noiseFloor,\n layer.noiseCeil,\n snoise(vec3(\n noiseCoord.x * layer.noiseFreq.x + time * layer.noiseFlow,\n noiseCoord.y * layer.noiseFreq.y,\n time * layer.noiseSpeed + layer.noiseSeed\n )) / 2.0 + 0.5\n );\n\n v_color = blendNormal(v_color, layer.color, pow(noise, 4.));\n }\n }\n\n //\n // Finish\n //\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);\n}", + noise: "//\n// Description : Array and textureless GLSL 2D/3D/4D simplex\n// noise functions.\n// Author : Ian McEwan, Ashima Arts.\n// Maintainer : stegu\n// Lastmod : 20110822 (ijm)\n// License : Copyright (C) 2011 Ashima Arts. All rights reserved.\n// Distributed under the MIT License. See LICENSE file.\n// https://github.com/ashima/webgl-noise\n// https://github.com/stegu/webgl-noise\n//\n\nvec3 mod289(vec3 x) {\n return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 mod289(vec4 x) {\n return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 permute(vec4 x) {\n return mod289(((x*34.0)+1.0)*x);\n}\n\nvec4 taylorInvSqrt(vec4 r)\n{\n return 1.79284291400159 - 0.85373472095314 * r;\n}\n\nfloat snoise(vec3 v)\n{\n const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;\n const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);\n\n// First corner\n vec3 i = floor(v + dot(v, C.yyy) );\n vec3 x0 = v - i + dot(i, C.xxx) ;\n\n// Other corners\n vec3 g = step(x0.yzx, x0.xyz);\n vec3 l = 1.0 - g;\n vec3 i1 = min( g.xyz, l.zxy );\n vec3 i2 = max( g.xyz, l.zxy );\n\n // x0 = x0 - 0.0 + 0.0 * C.xxx;\n // x1 = x0 - i1 + 1.0 * C.xxx;\n // x2 = x0 - i2 + 2.0 * C.xxx;\n // x3 = x0 - 1.0 + 3.0 * C.xxx;\n vec3 x1 = x0 - i1 + C.xxx;\n vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y\n vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y\n\n// Permutations\n i = mod289(i);\n vec4 p = permute( permute( permute(\n i.z + vec4(0.0, i1.z, i2.z, 1.0 ))\n + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))\n + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));\n\n// Gradients: 7x7 points over a square, mapped onto an octahedron.\n// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)\n float n_ = 0.142857142857; // 1.0/7.0\n vec3 ns = n_ * D.wyz - D.xzx;\n\n vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)\n\n vec4 x_ = floor(j * ns.z);\n vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)\n\n vec4 x = x_ *ns.x + ns.yyyy;\n vec4 y = y_ *ns.x + ns.yyyy;\n vec4 h = 1.0 - abs(x) - abs(y);\n\n vec4 b0 = vec4( x.xy, y.xy );\n vec4 b1 = vec4( x.zw, y.zw );\n\n //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;\n //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;\n vec4 s0 = floor(b0)*2.0 + 1.0;\n vec4 s1 = floor(b1)*2.0 + 1.0;\n vec4 sh = -step(h, vec4(0.0));\n\n vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;\n vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;\n\n vec3 p0 = vec3(a0.xy,h.x);\n vec3 p1 = vec3(a0.zw,h.y);\n vec3 p2 = vec3(a1.xy,h.z);\n vec3 p3 = vec3(a1.zw,h.w);\n\n//Normalise gradients\n vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));\n p0 *= norm.x;\n p1 *= norm.y;\n p2 *= norm.z;\n p3 *= norm.w;\n\n// Mix final noise value\n vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);\n m = m * m;\n return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),\n dot(p2,x2), dot(p3,x3) ) );\n}", + blend: "//\n// https://github.com/jamieowen/glsl-blend\n//\n\n// Normal\n\nvec3 blendNormal(vec3 base, vec3 blend) {\n\treturn blend;\n}\n\nvec3 blendNormal(vec3 base, vec3 blend, float opacity) {\n\treturn (blendNormal(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Screen\n\nfloat blendScreen(float base, float blend) {\n\treturn 1.0-((1.0-base)*(1.0-blend));\n}\n\nvec3 blendScreen(vec3 base, vec3 blend) {\n\treturn vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b));\n}\n\nvec3 blendScreen(vec3 base, vec3 blend, float opacity) {\n\treturn (blendScreen(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Multiply\n\nvec3 blendMultiply(vec3 base, vec3 blend) {\n\treturn base*blend;\n}\n\nvec3 blendMultiply(vec3 base, vec3 blend, float opacity) {\n\treturn (blendMultiply(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Overlay\n\nfloat blendOverlay(float base, float blend) {\n\treturn base<0.5?(2.0*base*blend):(1.0-2.0*(1.0-base)*(1.0-blend));\n}\n\nvec3 blendOverlay(vec3 base, vec3 blend) {\n\treturn vec3(blendOverlay(base.r,blend.r),blendOverlay(base.g,blend.g),blendOverlay(base.b,blend.b));\n}\n\nvec3 blendOverlay(vec3 base, vec3 blend, float opacity) {\n\treturn (blendOverlay(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Hard light\n\nvec3 blendHardLight(vec3 base, vec3 blend) {\n\treturn blendOverlay(blend,base);\n}\n\nvec3 blendHardLight(vec3 base, vec3 blend, float opacity) {\n\treturn (blendHardLight(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Soft light\n\nfloat blendSoftLight(float base, float blend) {\n\treturn (blend<0.5)?(2.0*base*blend+base*base*(1.0-2.0*blend)):(sqrt(base)*(2.0*blend-1.0)+2.0*base*(1.0-blend));\n}\n\nvec3 blendSoftLight(vec3 base, vec3 blend) {\n\treturn vec3(blendSoftLight(base.r,blend.r),blendSoftLight(base.g,blend.g),blendSoftLight(base.b,blend.b));\n}\n\nvec3 blendSoftLight(vec3 base, vec3 blend, float opacity) {\n\treturn (blendSoftLight(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Color dodge\n\nfloat blendColorDodge(float base, float blend) {\n\treturn (blend==1.0)?blend:min(base/(1.0-blend),1.0);\n}\n\nvec3 blendColorDodge(vec3 base, vec3 blend) {\n\treturn vec3(blendColorDodge(base.r,blend.r),blendColorDodge(base.g,blend.g),blendColorDodge(base.b,blend.b));\n}\n\nvec3 blendColorDodge(vec3 base, vec3 blend, float opacity) {\n\treturn (blendColorDodge(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Color burn\n\nfloat blendColorBurn(float base, float blend) {\n\treturn (blend==0.0)?blend:max((1.0-((1.0-base)/blend)),0.0);\n}\n\nvec3 blendColorBurn(vec3 base, vec3 blend) {\n\treturn vec3(blendColorBurn(base.r,blend.r),blendColorBurn(base.g,blend.g),blendColorBurn(base.b,blend.b));\n}\n\nvec3 blendColorBurn(vec3 base, vec3 blend, float opacity) {\n\treturn (blendColorBurn(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Vivid Light\n\nfloat blendVividLight(float base, float blend) {\n\treturn (blend<0.5)?blendColorBurn(base,(2.0*blend)):blendColorDodge(base,(2.0*(blend-0.5)));\n}\n\nvec3 blendVividLight(vec3 base, vec3 blend) {\n\treturn vec3(blendVividLight(base.r,blend.r),blendVividLight(base.g,blend.g),blendVividLight(base.b,blend.b));\n}\n\nvec3 blendVividLight(vec3 base, vec3 blend, float opacity) {\n\treturn (blendVividLight(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Lighten\n\nfloat blendLighten(float base, float blend) {\n\treturn max(blend,base);\n}\n\nvec3 blendLighten(vec3 base, vec3 blend) {\n\treturn vec3(blendLighten(base.r,blend.r),blendLighten(base.g,blend.g),blendLighten(base.b,blend.b));\n}\n\nvec3 blendLighten(vec3 base, vec3 blend, float opacity) {\n\treturn (blendLighten(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Linear burn\n\nfloat blendLinearBurn(float base, float blend) {\n\t// Note : Same implementation as BlendSubtractf\n\treturn max(base+blend-1.0,0.0);\n}\n\nvec3 blendLinearBurn(vec3 base, vec3 blend) {\n\t// Note : Same implementation as BlendSubtract\n\treturn max(base+blend-vec3(1.0),vec3(0.0));\n}\n\nvec3 blendLinearBurn(vec3 base, vec3 blend, float opacity) {\n\treturn (blendLinearBurn(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Linear dodge\n\nfloat blendLinearDodge(float base, float blend) {\n\t// Note : Same implementation as BlendAddf\n\treturn min(base+blend,1.0);\n}\n\nvec3 blendLinearDodge(vec3 base, vec3 blend) {\n\t// Note : Same implementation as BlendAdd\n\treturn min(base+blend,vec3(1.0));\n}\n\nvec3 blendLinearDodge(vec3 base, vec3 blend, float opacity) {\n\treturn (blendLinearDodge(base, blend) * opacity + base * (1.0 - opacity));\n}\n\n// Linear light\n\nfloat blendLinearLight(float base, float blend) {\n\treturn blend<0.5?blendLinearBurn(base,(2.0*blend)):blendLinearDodge(base,(2.0*(blend-0.5)));\n}\n\nvec3 blendLinearLight(vec3 base, vec3 blend) {\n\treturn vec3(blendLinearLight(base.r,blend.r),blendLinearLight(base.g,blend.g),blendLinearLight(base.b,blend.b));\n}\n\nvec3 blendLinearLight(vec3 base, vec3 blend, float opacity) {\n\treturn (blendLinearLight(base, blend) * opacity + base * (1.0 - opacity));\n}", + fragment: + "varying vec3 v_color;\n\nvoid main() {\n vec3 color = v_color;\n if (u_darken_top == 1.0) {\n vec2 st = gl_FragCoord.xy/resolution.xy;\n color.g -= pow(st.y + sin(-12.0) * st.x, u_shadow_power) * 0.4;\n }\n gl_FragColor = vec4(color, 1.0);\n}", + }), + (this.conf = { + presetName: "", + wireframe: false, + density: [0.06, 0.16], + zoom: 1, + rotation: 0, + playing: true, + }), + document.querySelectorAll("canvas").length < 1 + ? console.log("DID NOT LOAD HERO STRIPE CANVAS") + : ((this.minigl = new MiniGl(this.el, null, null, !0)), + requestAnimationFrame(() => { + this.el && ((this.computedCanvasStyle = getComputedStyle(this.el)), this.waitForCssVars()); + })); + /* + this.scrollObserver = await s.create(.1, !1), + this.scrollObserver.observe(this.el), + this.scrollObserver.onSeparate(() => { + window.removeEventListener("scroll", this.handleScroll), window.removeEventListener("mousedown", this.handleMouseDown), window.removeEventListener("mouseup", this.handleMouseUp), window.removeEventListener("keydown", this.handleKeyDown), this.isIntersecting = !1, this.conf.playing && this.pause() + }), + this.scrollObserver.onIntersect(() => { + window.addEventListener("scroll", this.handleScroll), window.addEventListener("mousedown", this.handleMouseDown), window.addEventListener("mouseup", this.handleMouseUp), window.addEventListener("keydown", this.handleKeyDown), this.isIntersecting = !0, this.addIsLoadedClass(), this.play() + })*/ + } + disconnect() { + this.scrollObserver && + (window.removeEventListener("scroll", this.handleScroll), + window.removeEventListener("mousedown", this.handleMouseDown), + window.removeEventListener("mouseup", this.handleMouseUp), + window.removeEventListener("keydown", this.handleKeyDown), + this.scrollObserver.disconnect()), + window.removeEventListener("resize", this.resize); + } + initMaterial() { + this.uniforms = { + u_time: new this.minigl.Uniform({ + value: 0, + }), + u_shadow_power: new this.minigl.Uniform({ + value: 5, + }), + u_darken_top: new this.minigl.Uniform({ + value: "" === this.el.dataset.jsDarkenTop ? 1 : 0, + }), + u_active_colors: new this.minigl.Uniform({ + value: this.activeColors, + type: "vec4", + }), + u_global: new this.minigl.Uniform({ + value: { + noiseFreq: new this.minigl.Uniform({ + value: [this.freqX, this.freqY], + type: "vec2", + }), + noiseSpeed: new this.minigl.Uniform({ + value: 5e-6, + }), + }, + type: "struct", + }), + u_vertDeform: new this.minigl.Uniform({ + value: { + incline: new this.minigl.Uniform({ + value: Math.sin(this.angle) / Math.cos(this.angle), + }), + offsetTop: new this.minigl.Uniform({ + value: -0.5, + }), + offsetBottom: new this.minigl.Uniform({ + value: -0.5, + }), + noiseFreq: new this.minigl.Uniform({ + value: [3, 4], + type: "vec2", + }), + noiseAmp: new this.minigl.Uniform({ + value: this.amp, + }), + noiseSpeed: new this.minigl.Uniform({ + value: 10, + }), + noiseFlow: new this.minigl.Uniform({ + value: 3, + }), + noiseSeed: new this.minigl.Uniform({ + value: this.seed, + }), + }, + type: "struct", + excludeFrom: "fragment", + }), + u_baseColor: new this.minigl.Uniform({ + value: this.sectionColors[0], + type: "vec3", + excludeFrom: "fragment", + }), + u_waveLayers: new this.minigl.Uniform({ + value: [], + excludeFrom: "fragment", + type: "array", + }), + }; + for (let e = 1; e < this.sectionColors.length; e += 1) + this.uniforms.u_waveLayers.value.push( + new this.minigl.Uniform({ + value: { + color: new this.minigl.Uniform({ + value: this.sectionColors[e], + type: "vec3", + }), + noiseFreq: new this.minigl.Uniform({ + value: [2 + e / this.sectionColors.length, 3 + e / this.sectionColors.length], + type: "vec2", + }), + noiseSpeed: new this.minigl.Uniform({ + value: 11 + 0.3 * e, + }), + noiseFlow: new this.minigl.Uniform({ + value: 6.5 + 0.3 * e, + }), + noiseSeed: new this.minigl.Uniform({ + value: this.seed + 10 * e, + }), + noiseFloor: new this.minigl.Uniform({ + value: 0.1, + }), + noiseCeil: new this.minigl.Uniform({ + value: 0.63 + 0.07 * e, + }), + }, + type: "struct", + }), + ); + return ( + (this.vertexShader = [this.shaderFiles.noise, this.shaderFiles.blend, this.shaderFiles.vertex].join( + "\n\n", + )), + new this.minigl.Material(this.vertexShader, this.shaderFiles.fragment, this.uniforms) + ); + } + initMesh() { + (this.material = this.initMaterial()), + (this.geometry = new this.minigl.PlaneGeometry()), + (this.mesh = new this.minigl.Mesh(this.geometry, this.material)); + } + shouldSkipFrame(e) { + return !!window.document.hidden || !this.conf.playing || parseInt(e, 10) % 2 == 0 || void 0; + } + updateFrequency(e) { + (this.freqX += e), (this.freqY += e); + } + toggleColor(index) { + this.activeColors[index] = 0 === this.activeColors[index] ? 1 : 0; + } + showGradientLegend() { + this.width > this.minWidth && + ((this.isGradientLegendVisible = !0), document.body.classList.add("isGradientLegendVisible")); + } + hideGradientLegend() { + (this.isGradientLegendVisible = !1), document.body.classList.remove("isGradientLegendVisible"); + } + init() { + this.initGradientColors(), + this.initMesh(), + this.resize(), + requestAnimationFrame(this.animate), + window.addEventListener("resize", this.resize); + } + /* + * Waiting for the css variables to become available, usually on page load before we can continue. + * Using default colors assigned below if no variables have been found after maxCssVarRetries + */ + waitForCssVars() { + if ( + this.computedCanvasStyle && + -1 !== this.computedCanvasStyle.getPropertyValue("--gradient-color-1").indexOf("#") + ) + this.init(), this.addIsLoadedClass(); + else { + if (((this.cssVarRetries += 1), this.cssVarRetries > this.maxCssVarRetries)) { + return (this.sectionColors = [16711680, 16711680, 16711935, 65280, 255]), void this.init(); + } + requestAnimationFrame(() => this.waitForCssVars()); + } + } + /* + * Initializes the four section colors by retrieving them from css variables. + */ + initGradientColors() { + this.sectionColors = ["--gradient-color-1", "--gradient-color-2", "--gradient-color-3", "--gradient-color-4"] + .map((cssPropertyName) => { + let hex = this.computedCanvasStyle.getPropertyValue(cssPropertyName).trim(); + //Check if shorthand hex value was used and double the length so the conversion in normalizeColor will work. + if (4 === hex.length) { + const hexTemp = hex + .substr(1) + .split("") + .map((hexTemp) => hexTemp + hexTemp) + .join(""); + hex = `#${hexTemp}`; + } + return hex && `0x${hex.substr(1)}`; + }) + .filter(Boolean) + .map(normalizeColor); + } +} + +/* + *Finally initializing the Gradient class, assigning a canvas to it and calling Gradient.connect() which initializes everything, + * Use Gradient.pause() and Gradient.play() for controls. + * + * Here are some default property values you can change anytime: + * Amplitude: Gradient.amp = 0 + * Colors: Gradient.sectionColors (if you change colors, use normalizeColor(#hexValue)) before you assign it. + * + * + * Useful functions + * Gradient.toggleColor(index) + * Gradient.updateFrequency(freq) + */ + +export { Gradient }; diff --git a/app/core/layouts/layout/loader.module.css b/app/core/layouts/layout/loader.module.css new file mode 100644 index 0000000..2ab5eba --- /dev/null +++ b/app/core/layouts/layout/loader.module.css @@ -0,0 +1,8 @@ +#gradientCanvas { + width: 100%; + height: 100%; + --gradient-color-1: #c3e4ff; + --gradient-color-2: #6ec3f4; + --gradient-color-3: #eae2ff; + --gradient-color-4: #b9beff; +} diff --git a/app/core/layouts/layout/loader.tsx b/app/core/layouts/layout/loader.tsx new file mode 100644 index 0000000..f589f9b --- /dev/null +++ b/app/core/layouts/layout/loader.tsx @@ -0,0 +1,26 @@ +import { useEffect } from "react"; + +import Logo from "../../components/logo"; +import { Gradient } from "./loader-gradient.js"; + +import styles from "./loader.module.css"; + +export default function Loader() { + useEffect(() => { + const gradient = new Gradient(); + // @ts-ignore + gradient.initGradient(`#${styles.gradientCanvas}`); + }, []); + + return ( +
+
+
+ + Loading up Shellphone... +
+
+ +
+ ); +} diff --git a/app/messages/pages/messages.tsx b/app/messages/pages/messages.tsx index a4825c4..f6f43cf 100644 --- a/app/messages/pages/messages.tsx +++ b/app/messages/pages/messages.tsx @@ -3,13 +3,14 @@ import type { BlitzPage } from "blitz"; import { Routes, dynamic } from "blitz"; import { atom, useAtom } from "jotai"; -import Layout from "app/core/layouts/layout"; +import AppLayout from "app/core/layouts/layout"; import ConversationsList from "../components/conversations-list"; import NewMessageButton from "../components/new-message-button"; import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; import useNotifications from "app/core/hooks/use-notifications"; import useCurrentUser from "app/core/hooks/use-current-user"; import PageTitle from "../../core/components/page-title"; +import Spinner from "../../core/components/spinner"; const Messages: BlitzPage = () => { const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); @@ -26,7 +27,7 @@ const Messages: BlitzPage = () => { return ( <> - + ); } @@ -35,7 +36,8 @@ const Messages: BlitzPage = () => { <>
- + }> + {/* TODO: skeleton conversations list */}
@@ -52,7 +54,7 @@ const NewMessageBottomSheet = dynamic(() => import("../components/new-message-bo export const bottomSheetOpenAtom = atom(false); -Messages.getLayout = (page) => {page}; +Messages.getLayout = (page) => {page}; Messages.authenticate = { redirectTo: Routes.SignIn().pathname }; diff --git a/app/messages/pages/messages/[recipient].tsx b/app/messages/pages/messages/[recipient].tsx index f8e9cc9..d75ac82 100644 --- a/app/messages/pages/messages/[recipient].tsx +++ b/app/messages/pages/messages/[recipient].tsx @@ -3,7 +3,7 @@ import type { BlitzPage } from "blitz"; import { Routes, useRouter } from "blitz"; import { IoChevronBack, IoInformationCircle, IoCall } from "react-icons/io5"; -import Layout from "../../../core/layouts/layout"; +import AppLayout from "../../../core/layouts/layout"; import Conversation from "../../components/conversation"; import useConversation from "../../hooks/use-conversation"; @@ -14,7 +14,7 @@ const ConversationPage: BlitzPage = () => { const conversation = useConversation(recipient)[0]; return ( - +
@@ -28,7 +28,7 @@ const ConversationPage: BlitzPage = () => { Loading messages with {recipient}
}> - + ); }; diff --git a/app/pages/_app.tsx b/app/pages/_app.tsx index 0ed7c5e..f9826a5 100644 --- a/app/pages/_app.tsx +++ b/app/pages/_app.tsx @@ -21,7 +21,7 @@ import "app/core/styles/index.css"; const { publicRuntimeConfig } = getConfig(); export default function App({ Component, pageProps }: AppProps) { - const session = useSession(); + const session = useSession({ suspense: false }); usePanelbear(publicRuntimeConfig.panelBear.siteId); useEffect(() => { if (session.userId) { @@ -42,7 +42,8 @@ export default function App({ Component, pageProps }: AppProps) { FallbackComponent={RootErrorFallback} onReset={useQueryErrorResetBoundary().reset} > - {getLayout()} + {/* TODO: better default fallback */} + {getLayout()} ); } diff --git a/app/pages/_offline.tsx b/app/pages/_offline.tsx index 7233ac5..dc07ee3 100644 --- a/app/pages/_offline.tsx +++ b/app/pages/_offline.tsx @@ -1,11 +1,11 @@ import { useRouter } from "blitz"; -import Layout from "../core/layouts/layout"; +import AppLayout from "../core/layouts/layout"; export default function Offline() { const router = useRouter(); return ( - +

Oops, looks like you went offline.

@@ -17,6 +17,6 @@ export default function Offline() {

-
+ ); } diff --git a/app/phone-calls/pages/calls.tsx b/app/phone-calls/pages/calls.tsx index 6932f48..b037778 100644 --- a/app/phone-calls/pages/calls.tsx +++ b/app/phone-calls/pages/calls.tsx @@ -2,11 +2,12 @@ import { Suspense } from "react"; import type { BlitzPage } from "blitz"; import { Routes } from "blitz"; -import Layout from "app/core/layouts/layout"; +import AppLayout from "app/core/layouts/layout"; import PhoneCallsList from "../components/phone-calls-list"; import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; import useCurrentUser from "app/core/hooks/use-current-user"; import PageTitle from "../../core/components/page-title"; +import Spinner from "../../core/components/spinner"; const PhoneCalls: BlitzPage = () => { const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); @@ -15,7 +16,7 @@ const PhoneCalls: BlitzPage = () => { return ( <> - + ); } @@ -24,7 +25,8 @@ const PhoneCalls: BlitzPage = () => { <>
- + }> + {/* TODO: skeleton phone calls list */}
@@ -32,7 +34,7 @@ const PhoneCalls: BlitzPage = () => { ); }; -PhoneCalls.getLayout = (page) => {page}; +PhoneCalls.getLayout = (page) => {page}; PhoneCalls.authenticate = { redirectTo: Routes.SignIn() }; diff --git a/app/phone-calls/pages/keypad.tsx b/app/phone-calls/pages/keypad.tsx index c7ddc24..a4d4230 100644 --- a/app/phone-calls/pages/keypad.tsx +++ b/app/phone-calls/pages/keypad.tsx @@ -7,7 +7,7 @@ import { Transition } from "@headlessui/react"; import { IoBackspace, IoCall } from "react-icons/io5"; import { Direction } from "db"; -import Layout from "app/core/layouts/layout"; +import AppLayout from "app/core/layouts/layout"; import Keypad from "../components/keypad"; import usePhoneCalls from "../hooks/use-phone-calls"; import useKeyPress from "../hooks/use-key-press"; @@ -161,7 +161,7 @@ const pressBackspaceAtom = atom(null, (get, set) => { set(phoneNumberAtom, (prevState) => prevState.slice(0, -1)); }); -KeypadPage.getLayout = (page) => {page}; +KeypadPage.getLayout = (page) => {page}; KeypadPage.authenticate = { redirectTo: Routes.SignIn() }; diff --git a/app/settings/components/billing/billing-history.tsx b/app/settings/components/billing/billing-history.tsx index fb8472f..82f9f59 100644 --- a/app/settings/components/billing/billing-history.tsx +++ b/app/settings/components/billing/billing-history.tsx @@ -10,10 +10,11 @@ export default function BillingHistory() { skip, pagesNumber, currentPage, - goToPreviousPage, + lastPage, hasPreviousPage, - goToNextPage, hasNextPage, + goToPreviousPage, + goToNextPage, setPage, } = usePaymentsHistory(); @@ -104,8 +105,8 @@ export default function BillingHistory() { Previous

- Page 1 of{" "} - 4 + Page {currentPage} of{" "} + {lastPage}

diff --git a/app/settings/components/settings-layout.tsx b/app/settings/components/settings-layout.tsx index 9e0f58d..c24e827 100644 --- a/app/settings/components/settings-layout.tsx +++ b/app/settings/components/settings-layout.tsx @@ -1,4 +1,5 @@ import type { FunctionComponent } from "react"; +import { Suspense } from "react"; import { Link, Routes, useMutation, useRouter } from "blitz"; import clsx from "clsx"; import { @@ -10,9 +11,10 @@ import { IoPersonCircleOutline, } from "react-icons/io5"; -import Layout from "app/core/layouts/layout"; -import logout from "app/auth/mutations/logout"; +import AppLayout from "app/core/layouts/layout"; import Divider from "./divider"; +import Spinner from "../../core/components/spinner"; +import logout from "app/auth/mutations/logout"; const subNavigation = [ { name: "Account", href: Routes.Account(), icon: IoPersonCircleOutline }, @@ -26,7 +28,7 @@ const SettingsLayout: FunctionComponent = ({ children }) => { const [logoutMutation] = useMutation(logout); return ( - +
@@ -43,7 +45,7 @@ const SettingsLayout: FunctionComponent = ({ children }) => { const isCurrentPage = item.href.pathname === router.pathname; return ( - + { -
{children}
+
+ }>{children} +
- + ); }; diff --git a/app/settings/hooks/use-payments-history.ts b/app/settings/hooks/use-payments-history.ts index 4f234f6..a0fa243 100644 --- a/app/settings/hooks/use-payments-history.ts +++ b/app/settings/hooks/use-payments-history.ts @@ -14,6 +14,7 @@ export default function usePaymentsHistory() { .fill(-1) .map((_, i) => i + 1); const currentPage = Math.floor((skip / count) * totalPages) + 1; + const lastPage = pagesNumber[pagesNumber.length - 1]; const hasPreviousPage = skip > 0; const hasNextPage = hasMore && !!nextPage; const goToPreviousPage = () => hasPreviousPage && setSkip(skip - itemsPerPage); @@ -26,6 +27,7 @@ export default function usePaymentsHistory() { skip, pagesNumber, currentPage, + lastPage, hasPreviousPage, hasNextPage, goToPreviousPage, diff --git a/app/settings/pages/settings/phone.tsx b/app/settings/pages/settings/phone.tsx index 7ebdce1..e4c7d60 100644 --- a/app/settings/pages/settings/phone.tsx +++ b/app/settings/pages/settings/phone.tsx @@ -1,4 +1,3 @@ -import { Suspense } from "react"; import type { BlitzPage } from "blitz"; import { Routes, dynamic } from "blitz"; @@ -8,10 +7,8 @@ import PhoneNumberForm from "../../components/phone/phone-number-form"; const PhoneSettings: BlitzPage = () => { return (
- - - - + +
); };