diff --git a/heat-stack/app/App.css b/heat-stack/app/App.css
new file mode 100644
index 00000000..2fb7983c
--- /dev/null
+++ b/heat-stack/app/App.css
@@ -0,0 +1,78 @@
+body {
+ font-family: 'Inter', sans-serif;
+}
+
+.main-container {
+ grid-area: main;
+ overflow-y: auto;
+ padding: 20px 20px;
+ margin: 30px 30px;
+}
+
+.page-title {
+ color: #000;
+ font-size: 30px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 36px;
+ letter-spacing: -0.225px;
+ text-align: center;
+ padding-block: 20px;
+ /*vertical padding */
+}
+.section-title {
+color: var(--slate-700, #334155);
+ font-size: 24px;
+ font-style: normal;
+ line-height: 32px;
+ letter-spacing: -0.144px;
+ padding-block: 20px;
+ /*vertical padding */
+}
+
+.item-group-title {
+ color: var(--slate-900, #0F172A);
+ /* h2 */
+ font-size: 30px;
+ font-style: normal;
+ line-height: 36px;
+ /* 120% */
+ letter-spacing: -0.225px;
+ padding-block: 20px;
+ /*vertical padding */
+}
+.item-title {
+color: var(--slate-900, #0F172A);
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 28px;
+ padding-block: 20px;
+ /*vertical padding */
+}
+.item-title-small {
+color: var(--slate-900, #0F172A);
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 28px;
+ padding-block: 20px;
+ /*vertical padding */
+}
+
+.item-big {
+ color: var(--slate-900, #0F172A);
+ font-size: 30px;
+ font-weight: 300;
+ font-style: normal;
+ line-height: 36px;
+ letter-spacing: -0.225px;
+}
+.item {
+ color: var(--slate-900, #0F172A);
+ font-size: 20px;
+ font-weight: 300;
+ font-style: normal;
+ line-height: 28px;
+}
+
diff --git a/heat-stack/app/components/CaseSummary.tsx b/heat-stack/app/components/CaseSummary.tsx
new file mode 100644
index 00000000..b77bc392
--- /dev/null
+++ b/heat-stack/app/components/CaseSummary.tsx
@@ -0,0 +1,18 @@
+import { CurrentHeatingSystem } from './ui/CaseSummaryComponents/CurrentHeatingSystem.tsx'
+import { EnergyUseHistory } from './ui/CaseSummaryComponents/EnergyUseHistory.tsx'
+import { Graphs } from './ui/CaseSummaryComponents/Graphs.tsx'
+import { HomeInformation } from './ui/CaseSummaryComponents/HomeInformation.tsx'
+
+export function CaseSummary() {
+ return (
+
+
+
Case Summary
+
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/CurrentHeatingSystem.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/CurrentHeatingSystem.tsx
new file mode 100644
index 00000000..11833b8b
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/CurrentHeatingSystem.tsx
@@ -0,0 +1,39 @@
+export function CurrentHeatingSystem() {
+ const fuelType = 'Natural Gas'
+ const heatingSystemEfficiency = '75'
+ const setPoint = '70'
+ const setbackTemperature = '65'
+ const setbackTime = '7'
+
+ return (
+
+ Current Heating System
+
+
+
+
+ Fuel Type
+
+
{fuelType}
+ Heating System Efficiency (%)
+
+
{heatingSystemEfficiency}
+
+
+
+
+
Thermostat Settings
+
+ Set Point (°F)
+
{setPoint}
+ Setback Temperature (°F)
+
+
{setbackTemperature}
+ Setback Time (h)
+
{setbackTime}
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistory.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistory.tsx
new file mode 100644
index 00000000..d0809501
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistory.tsx
@@ -0,0 +1,52 @@
+import { EnergyUseHistoryChart } from './EnergyUseHistoryChart.tsx'
+
+export function EnergyUseHistory() {
+ const averageIndoorTemperature = '63.5'
+ const dailyOtherUsage = '1.07'
+ const balancePoint = '60.5'
+ const numPeriodsIncluded = '30 / 36'
+ const standardDevationUA = '5.52'
+ const wholeHomeUA = '1,112'
+ const fileName = '20200930_Eversource.csv'
+
+ return (
+
+ Energy Use History
+
+
+ Data Source
+
+
{fileName}
+
+
Analysis
+
+
+
+ Average Indoor Temperature (°F)
+
{averageIndoorTemperature}
+ Daily Other Usage
+
{dailyOtherUsage}
+
+
+
+
+ Balance Point (°F)
+
{balancePoint}
+ No. of Periods Included
+
{numPeriodsIncluded}
+
+
+
+
+ Standard Deviation of UA (%)
+
{standardDevationUA}
+ Whole-home UA (BTU/h-°F)
+
{wholeHomeUA}
+
+
+
+
Usage Details
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistoryChart.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistoryChart.tsx
new file mode 100644
index 00000000..595b0f5e
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/EnergyUseHistoryChart.tsx
@@ -0,0 +1,62 @@
+import { Checkbox } from '../../../components/ui/checkbox.tsx'
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '../../../components/ui/table.tsx'
+
+const months = [
+ {
+ includeData: true,
+ startDate: '02/02/2018',
+ endDate: '02/28/2018',
+ daysInBill: '27',
+ usage: 'Yes',
+ fUA: '10',
+ },
+ {
+ includeData: true,
+ startDate: '03/01/2018',
+ endDate: '03/31/2018',
+ daysInBill: '31',
+ usage: 'Modest',
+ fUA: '30',
+ },
+]
+
+export function EnergyUseHistoryChart() {
+ return (
+
+
+
+ #
+ Include Data
+ Start Date
+ End Date
+ Days in Bill
+ Usage (therms)
+ 60.5 °F UA (BTU/h-F)
+
+
+
+ {months.map((month, index) => (
+
+ {index + 1}
+
+
+
+ {month.startDate}
+ {month.endDate}
+ {month.daysInBill}
+ {month.usage}
+ {month.fUA}
+
+ ))}
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/Graphs.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs.tsx
new file mode 100644
index 00000000..48cbcc9a
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs.tsx
@@ -0,0 +1,15 @@
+import { HeatLoad } from './Graphs/HeatLoad.tsx'
+import { StandardDeviationOfUA } from './Graphs/StandardDeviationOfUA.tsx'
+import { WholeHomeUAComparison } from './Graphs/WholeHomeUAComparison.tsx'
+
+export function Graphs() {
+ return (
+
+ Graphs
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/HeatLoad.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/HeatLoad.tsx
new file mode 100644
index 00000000..accb2a0c
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/HeatLoad.tsx
@@ -0,0 +1,48 @@
+import {
+ ScatterChart,
+ Scatter,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from 'recharts'
+
+// data from Karle Heat Load Analysis Beta 7 2023-07-11
+const data = [
+ { x: 0, y: 74015 },
+ { x: 60.5, y: 10045 },
+ { x: 67, y: 3172 },
+ { x: 70, y: 0 },
+ { x: 8.4, y: 65133 },
+]
+
+export function HeatLoad() {
+ return (
+
+
Heat Load
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/StandardDeviationOfUA.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/StandardDeviationOfUA.tsx
new file mode 100644
index 00000000..3a37c1e5
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/StandardDeviationOfUA.tsx
@@ -0,0 +1,53 @@
+import {
+ ScatterChart,
+ Scatter,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from 'recharts'
+
+// data from Karle Heat Load Analysis Beta 7 2023-07-11
+const data = [
+ { x: 58.5, y: 0.0534 },
+ { x: 60.5, y: 0.0508 },
+ { x: 62.5, y: 0.0528 },
+]
+
+export function StandardDeviationOfUA() {
+ return (
+
+
Standard Deviation of UA
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/WholeHomeUAComparison.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/WholeHomeUAComparison.tsx
new file mode 100644
index 00000000..54788c6b
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/Graphs/WholeHomeUAComparison.tsx
@@ -0,0 +1,149 @@
+import {
+ ComposedChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ Scatter,
+ ResponsiveContainer,
+} from 'recharts'
+
+const data = [
+ { x: 2702, y: 1092 },
+ { x: 4000, y: 1155 },
+ { x: 1851, y: 464 },
+ { x: 1112, y: 218 },
+ { x: 3000, y: 733 },
+ { x: 2100, y: 798 },
+ { x: 2454, y: 475 },
+ { x: 1229, y: 341 },
+ { x: 2000, y: 720 },
+ { x: 3946, y: 648 },
+ { x: 1960, y: 477 },
+ { x: 1800, y: 551 },
+ { x: 2992, y: 751 },
+ { x: 2342, y: 698 },
+ { x: 1728, y: 290 },
+ { x: 2440, y: 977 },
+ { x: 3891, y: 1300 },
+ { x: 1906, y: 958 },
+ { x: 2835, y: 967 },
+ { x: 3459, y: 975 },
+ { x: 3459, y: 809 },
+ { x: 4230, y: 1684 },
+ { x: 4250, y: 1265 },
+ { x: 3604, y: 1422 },
+ { x: 2033, y: 715 },
+ { x: 4000, y: 1124 },
+ { x: 3028, y: 1519 },
+ { x: 3254.6, y: 872 },
+ { x: 3514, y: 743 },
+ { x: 1896, y: 728 },
+ { x: 3878, y: 1011 },
+ { x: 2760, y: 792 },
+ { x: 3262, y: 1066 },
+ { x: 3850, y: 1128 },
+ { x: 1559, y: 528 },
+ { x: 2133, y: 709 },
+ { x: 2400, y: 994 },
+ { x: 3370, y: 1072 },
+ { x: 1662, y: 775 },
+ { x: 1533, y: 645 },
+ { x: 2600, y: 587 },
+ { x: 2155, y: 654 },
+ { x: 2200, y: 567 },
+ { x: 1999, y: 716 },
+ { x: 2400, y: 758 },
+ { x: 2492, y: 858 },
+ { x: 3120, y: 857 },
+ { x: 3398, y: 1147 },
+ { x: 4857, y: 1085 },
+ { x: 3254, y: 723 },
+ { x: 2584, y: 872 },
+ { x: 2688, y: 670 },
+ { x: 864, y: 479 },
+ { x: 2110, y: 609 },
+ { x: 1500, y: 767 },
+ { x: 2803, y: 648 },
+ { x: 1157, y: 340 },
+ { x: 5000, y: 1485 },
+ { x: 2228, y: 627 },
+ { x: 1258, y: 417 },
+ { x: 2500, y: 951 },
+ { x: 1700, y: 721 },
+ { x: 3066, y: 1622 },
+ { x: 2485, y: 735 },
+ { x: 1300, y: 435 },
+ { x: 1600, y: 356 },
+ { x: 2716, y: 820 },
+ { x: 3000, y: 1207 },
+ { x: 2000, y: 599 },
+ { x: 1980, y: 513 },
+ { x: 2500, y: 901 },
+ { x: 2940, y: 1020 },
+ { x: 2078, y: 699 },
+ { x: 2824, y: 849 },
+ { x: 2140, y: 913 },
+ { x: 2765, y: 900 },
+ { x: 3378, y: 944 },
+ { x: 3111, y: 823 },
+ { x: 2200, y: 680 },
+ { x: 3800, y: 1057 },
+ { x: 1638, y: 849 },
+ { x: 2076, y: 992 },
+ { x: 3740, y: 1207 },
+ { x: 1566, y: 398 },
+ { x: 2508, y: 867 },
+ { x: 1518, y: 480 },
+ { x: 1361, y: 565 },
+ { x: 3886, y: 985 },
+ { x: 2263, y: 1042 },
+ { x: 1970, y: 479 },
+ { x: 2133, y: 564 },
+ { x: 1624, y: 571 },
+ { x: 2093, y: 418 },
+ { x: 2028, y: 651 },
+ { x: 2849, y: 799 },
+ { x: 0, yLine: 0 },
+ { x: 5000, yLine: 1650 },
+]
+
+export function WholeHomeUAComparison() {
+ return (
+
+
Whole Home UA Comparison
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/CaseSummaryComponents/HomeInformation.tsx b/heat-stack/app/components/ui/CaseSummaryComponents/HomeInformation.tsx
new file mode 100644
index 00000000..ad560b2e
--- /dev/null
+++ b/heat-stack/app/components/ui/CaseSummaryComponents/HomeInformation.tsx
@@ -0,0 +1,64 @@
+import { Form } from '@remix-run/react'
+import { Input } from '../input.tsx'
+import { Label } from '../label.tsx'
+
+export function HomeInformation() {
+ const name = 'Pietro Schirano'
+ const street = '567 Pine Avenue Apt 21'
+ const city = 'Rivertown'
+ const state = 'MA'
+ const zip = '02856'
+ const country = 'United States of America'
+ const livingArea = '3,000'
+ const designTemperature = '63'
+ const designTemperatureOverride = '65'
+ return (
+
+ Home Information
+
+
+
+
+ Resident / Client
+
+
{name}
+ Address
+
{street}
+
+ {city}, {state}, {zip}
+
+
{country}
+ Living Area (sf)
+
{livingArea}
+
+
+
+
+
+ Design Temperature (°F)
+
{designTemperature}
+
+
+
+
+
+ )
+}
diff --git a/heat-stack/app/components/ui/README.md b/heat-stack/app/components/ui/README.md
index 433847df..1ad189cb 100644
--- a/heat-stack/app/components/ui/README.md
+++ b/heat-stack/app/components/ui/README.md
@@ -4,4 +4,4 @@ Some components in this directory are downloaded via the
[shadcn/ui](https://ui.shadcn.com) [CLI](https://ui.shadcn.com/docs/cli). Feel
free to customize them to your needs. It's important to know that shadcn/ui is
not a library of components you install, but instead it's a registry of prebuilt
-components which you can download and customize.
+components which you can download and customize.
\ No newline at end of file
diff --git a/heat-stack/app/components/ui/checkbox.tsx b/heat-stack/app/components/ui/checkbox.tsx
index 637a7fdd..298447c9 100644
--- a/heat-stack/app/components/ui/checkbox.tsx
+++ b/heat-stack/app/components/ui/checkbox.tsx
@@ -1,5 +1,6 @@
-import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
import * as React from 'react'
+import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
+import { Check } from 'lucide-react'
import { cn } from '#app/utils/misc.tsx'
@@ -25,14 +26,15 @@ const Checkbox = React.forwardRef<
-
+
+ {/*
-
+ */}
))
diff --git a/heat-stack/app/components/ui/table.tsx b/heat-stack/app/components/ui/table.tsx
new file mode 100644
index 00000000..ba987225
--- /dev/null
+++ b/heat-stack/app/components/ui/table.tsx
@@ -0,0 +1,117 @@
+import * as React from "react"
+
+import { cn } from "#app/utils/misc.tsx"
+
+const Table = React.forwardRef<
+ HTMLTableElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Table.displayName = "Table"
+
+const TableHeader = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableHeader.displayName = "TableHeader"
+
+const TableBody = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableBody.displayName = "TableBody"
+
+const TableFooter = React.forwardRef<
+ HTMLTableSectionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+ tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+))
+TableFooter.displayName = "TableFooter"
+
+const TableRow = React.forwardRef<
+ HTMLTableRowElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableRow.displayName = "TableRow"
+
+const TableHead = React.forwardRef<
+ HTMLTableCellElement,
+ React.ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableHead.displayName = "TableHead"
+
+const TableCell = React.forwardRef<
+ HTMLTableCellElement,
+ React.TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+ HTMLTableCaptionElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/heat-stack/app/root.tsx b/heat-stack/app/root.tsx
index 9225a479..ec56625d 100644
--- a/heat-stack/app/root.tsx
+++ b/heat-stack/app/root.tsx
@@ -1,86 +1,22 @@
-import { useForm } from '@conform-to/react'
-import { parse } from '@conform-to/zod'
import { cssBundleHref } from '@remix-run/css-bundle'
-import {
- json,
- type DataFunctionArgs,
- type HeadersFunction,
- type LinksFunction,
- type MetaFunction,
-} from '@remix-run/node'
-import {
- Form,
- Link,
- Links,
- LiveReload,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
- useFetcher,
- useFetchers,
- useLoaderData,
- useMatches,
- useSubmit,
-} from '@remix-run/react'
-import { withSentry } from '@sentry/remix'
-import { Suspense, lazy, useEffect, useRef, useState } from 'react'
-import { object, z } from 'zod'
-import { Confetti } from './components/confetti.tsx'
-import { GeneralErrorBoundary } from './components/error-boundary.tsx'
-import { ErrorList } from './components/forms.tsx'
-import { SearchBar } from './components/search-bar.tsx'
-import { EpicToaster } from './components/toaster.tsx'
-import { Button } from './components/ui/button.tsx'
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuPortal,
- DropdownMenuTrigger,
-} from './components/ui/dropdown-menu.tsx'
-import { Icon, href as iconsHref } from './components/ui/icon.tsx'
import fontStyleSheetUrl from './styles/font.css'
import tailwindStyleSheetUrl from './styles/tailwind.css'
-import { authenticator, getUserId } from './utils/auth.server.ts'
-import { ClientHintCheck, getHints, useHints } from './utils/client-hints.tsx'
-import { getConfetti } from './utils/confetti.server.ts'
-import { prisma } from './utils/db.server.ts'
-import { getEnv } from './utils/env.server.ts'
-import {
- combineHeaders,
- getDomainUrl,
- getUserImgSrc,
- invariantResponse,
-} from './utils/misc.tsx'
+import { Links, Scripts } from '@remix-run/react'
+import { href as iconsHref } from './components/ui/icon.tsx'
+import { DataFunctionArgs, json, type LinksFunction } from '@remix-run/node'
+
+import { CaseSummary } from './components/CaseSummary.tsx'
+import './App.css'
import { useNonce } from './utils/nonce-provider.ts'
-import { useRequestInfo } from './utils/request-info.ts'
-import { type Theme, setTheme, getTheme } from './utils/theme.server.ts'
import { makeTimings, time } from './utils/timing.server.ts'
-import { getToast } from './utils/toast.server.ts'
-import { useOptionalUser, useUser } from './utils/user.ts'
-
+import { combineHeaders, getDomainUrl } from './utils/misc.tsx'
+// import { csrf } from './utils/csrf.server.ts'
+import { getEnv } from './utils/env.server.ts'
+import { getHints } from './utils/client-hints.tsx'
import { WeatherExample } from './components/WeatherExample.tsx'
-import type { Weather } from './WeatherExample.d.ts';
-
-import * as pyodideModule from 'pyodide'
-import engine from '../../rules-engine/src/rules_engine/engine.py';
-
-const getPyodide = async () => {
- // public folder:
- return await pyodideModule.loadPyodide({
- indexURL: 'pyodide-env/',
- })
-}
-
-const runPythonScript = async () => {
- const pyodide: any = await getPyodide();
- // console.log(engine);
- await pyodide.loadPackage("numpy")
- await pyodide.runPythonAsync(engine);
- return pyodide;
-};
-
+import { Weather } from './WeatherExample.js'
+import { getUserId } from './utils/auth.server.ts'
+import { prisma } from './utils/db.server.ts'
export const links: LinksFunction = () => {
return [
@@ -110,14 +46,6 @@ export const links: LinksFunction = () => {
].filter(Boolean)
}
-export const meta: MetaFunction = ({ data }) => {
- return [
- { title: data ? 'Epic Notes' : 'Error | Epic Notes' },
- { name: 'description', content: `Your own captain's log` },
- ]
-}
-
-// We can only have one `loader()`. More requires special gymnastics.
export async function loader({ request }: DataFunctionArgs) {
const timings = makeTimings('root loader')
const userId = await time(() => getUserId(request), {
@@ -125,7 +53,6 @@ export async function loader({ request }: DataFunctionArgs) {
type: 'getUserId',
desc: 'getUserId in root',
})
-
const user = userId
? await time(
() =>
@@ -149,346 +76,57 @@ export async function loader({ request }: DataFunctionArgs) {
{ timings, type: 'find user', desc: 'find user in root' },
)
: null
- if (userId && !user) {
- console.info('something weird happened')
- // something weird happened... The user is authenticated but we can't find
- // them in the database. Maybe they were deleted? Let's log them out.
- await authenticator.logout(request, { redirectTo: '/' })
- }
- const { toast, headers: toastHeaders } = await getToast(request)
- const { confettiId, headers: confettiHeaders } = getConfetti(request)
-
+ // const honeyProps = honeypot.getInputProps()
+ // const [csrfToken, csrfCookieHeader] = await csrf.commitToken()
// Weather station data
- const w_href: string = 'https://archive-api.open-meteo.com/v1/archive?latitude=52.52&longitude=13.41&daily=temperature_2m_max&timezone=America%2FNew_York&start_date=2022-01-01&end_date=2023-08-30&temperature_unit=fahrenheit';
- const w_res: Response = await fetch(w_href);
+ const w_href: string =
+ 'https://archive-api.open-meteo.com/v1/archive?latitude=52.52&longitude=13.41&daily=temperature_2m_max&timezone=America%2FNew_York&start_date=2022-01-01&end_date=2023-08-30&temperature_unit=fahrenheit'
+ const w_res: Response = await fetch(w_href)
const weather: Weather = (await w_res.json()) as Weather
return json(
{
weather: weather,
- user,
+ user: user,
requestInfo: {
hints: getHints(request),
origin: getDomainUrl(request),
path: new URL(request.url).pathname,
- userPrefs: {
- theme: getTheme(request),
- },
+ userPrefs: {},
},
ENV: getEnv(),
- toast,
- confettiId,
+ // honeyProps,
+ // csrfToken,
},
{
headers: combineHeaders(
{ 'Server-Timing': timings.toString() },
- toastHeaders,
- confettiHeaders,
+ // csrfCookieHeader ? { 'set-cookie': csrfCookieHeader } : null,
),
},
)
}
-export const headers: HeadersFunction = ({ loaderHeaders }) => {
- const headers = {
- 'Server-Timing': loaderHeaders.get('Server-Timing') ?? '',
- }
- return headers
-}
-
-const ThemeFormSchema = z.object({
- theme: z.enum(['system', 'light', 'dark']),
-})
-
-export async function action({ request }: DataFunctionArgs) {
- const formData = await request.formData()
- invariantResponse(
- formData.get('intent') === 'update-theme',
- 'Invalid intent',
- { status: 400 },
- )
- const submission = parse(formData, {
- schema: ThemeFormSchema,
- })
- if (submission.intent !== 'submit') {
- return json({ status: 'success', submission } as const)
- }
- if (!submission.value) {
- return json({ status: 'error', submission } as const, { status: 400 })
- }
- const { theme } = submission.value
-
- const responseInit = {
- headers: { 'set-cookie': setTheme(theme) },
- }
- return json({ success: true, submission }, responseInit)
-}
-
-function Document({
- children,
- nonce,
- theme = 'light',
- env = {},
-}: {
- children: React.ReactNode
- nonce: string
- theme?: Theme
- env?: Record
-}) {
- const [output, setOutput] = useState('(loading python...)');
-
- useEffect(() => {
- const run = async () => {
- const pyodide: any = await runPythonScript();
- // console.log(pyodide);
- const result = await pyodide.runPythonAsync("hdd(57, 60)");
- setOutput(result);
- };
- run();
- }, []);
+export default function HeatStack({ env = {} }) {
+ const nonce = useNonce()
return (
-
+
-
-
-
-
- {children}
-
-
-
-
+
+
+ left{nonce}right
+
)
}
-
-function App() {
- const data = useLoaderData()
- const nonce = useNonce()
- const user = useOptionalUser()
- const theme = useTheme()
- const matches = useMatches()
- const isOnSearchPage = matches.find(m => m.id === 'routes/users+/index')
-
- return (
-
-
-
-
-
- )
-}
-export default withSentry(App)
-
-function UserDropdown() {
- const user = useUser()
- const submit = useSubmit()
- const formRef = useRef(null)
- return (
-
-
-
- e.preventDefault()}
- className="flex items-center gap-2"
- >
-
-
- {user.name ?? user.username}
-
-
-
-
-
-
-
-
-
- Profile
-
-
-
-
-
-
- Notes
-
-
-
- {
- event.preventDefault()
- submit(formRef.current)
- }}
- >
-
-
-
-
-
- )
-}
-
-/**
- * @returns the user's theme preference, or the client hint theme if the user
- * has not set a preference.
- */
-export function useTheme() {
- const hints = useHints()
- const requestInfo = useRequestInfo()
- const optimisticMode = useOptimisticThemeMode()
- if (optimisticMode) {
- return optimisticMode === 'system' ? hints.theme : optimisticMode
- }
- return requestInfo.userPrefs.theme ?? hints.theme
-}
-
-/**
- * If the user's changing their theme mode preference, this will return the
- * value it's being changed to.
- */
-export function useOptimisticThemeMode() {
- const fetchers = useFetchers()
-
- const themeFetcher = fetchers.find(
- f => f.formData?.get('intent') === 'update-theme',
- )
-
- if (themeFetcher && themeFetcher.formData) {
- const submission = parse(themeFetcher.formData, {
- schema: ThemeFormSchema,
- })
- return submission.value?.theme
- }
-}
-
-function ThemeSwitch({ userPreference }: { userPreference?: Theme | null }) {
- const fetcher = useFetcher()
-
- const [form] = useForm({
- id: 'theme-switch',
- lastSubmission: fetcher.data?.submission,
- onValidate({ formData }) {
- return parse(formData, { schema: ThemeFormSchema })
- },
- })
-
- const optimisticMode = useOptimisticThemeMode()
- const mode = optimisticMode ?? userPreference ?? 'system'
- const nextMode =
- mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
- const modeLabel = {
- light: (
-
- Light
-
- ),
- dark: (
-
- Dark
-
- ),
- system: (
-
- System
-
- ),
- }
-
- return (
-
-
-
-
- {modeLabel[mode]}
-
-
-
-
- )
-}
-
-export function ErrorBoundary() {
- // the nonce doesn't rely on the loader so we can access that
- const nonce = useNonce()
-
- // NOTE: you cannot use useLoaderData in an ErrorBoundary because the loader
- // likely failed to run so we have to do the best we can.
- // We could probably do better than this (it's possible the loader did run).
- // This would require a change in Remix.
-
- // Just make sure your root route never errors out and you'll always be able
- // to give the user a better UX.
-
- return (
-
-
-
- )
-}
diff --git a/heat-stack/app/root_original.tsx b/heat-stack/app/root_original.tsx
new file mode 100644
index 00000000..ab3b6381
--- /dev/null
+++ b/heat-stack/app/root_original.tsx
@@ -0,0 +1,502 @@
+import { useForm } from '@conform-to/react'
+import { parse } from '@conform-to/zod'
+import { cssBundleHref } from '@remix-run/css-bundle'
+import {
+ json,
+ type DataFunctionArgs,
+ type HeadersFunction,
+ type LinksFunction,
+ type MetaFunction,
+} from '@remix-run/node'
+import {
+ Form,
+ Link,
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+ useFetcher,
+ useFetchers,
+ useLoaderData,
+ useMatches,
+ useSubmit,
+} from '@remix-run/react'
+import { withSentry } from '@sentry/remix'
+import { Suspense, lazy, useEffect, useRef, useState } from 'react'
+import { object, z } from 'zod'
+import { Confetti } from './components/confetti.tsx'
+import { GeneralErrorBoundary } from './components/error-boundary.tsx'
+import { ErrorList } from './components/forms.tsx'
+import { SearchBar } from './components/search-bar.tsx'
+import { EpicToaster } from './components/toaster.tsx'
+import { Button } from './components/ui/button.tsx'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+} from './components/ui/dropdown-menu.tsx'
+import { Icon, href as iconsHref } from './components/ui/icon.tsx'
+import fontStyleSheetUrl from './styles/font.css'
+import tailwindStyleSheetUrl from './styles/tailwind.css'
+import { authenticator, getUserId } from './utils/auth.server.ts'
+import { ClientHintCheck, getHints, useHints } from './utils/client-hints.tsx'
+import { getConfetti } from './utils/confetti.server.ts'
+import { prisma } from './utils/db.server.ts'
+import { getEnv } from './utils/env.server.ts'
+import {
+ combineHeaders,
+ getDomainUrl,
+ getUserImgSrc,
+ invariantResponse,
+} from './utils/misc.tsx'
+import { useNonce } from './utils/nonce-provider.ts'
+import { useRequestInfo } from './utils/request-info.ts'
+import { type Theme, setTheme, getTheme } from './utils/theme.server.ts'
+import { makeTimings, time } from './utils/timing.server.ts'
+import { getToast } from './utils/toast.server.ts'
+import { useOptionalUser, useUser } from './utils/user.ts'
+
+import { WeatherExample } from './components/WeatherExample.tsx'
+import type { Weather } from './WeatherExample.d.ts'
+
+import * as pyodideModule from 'pyodide'
+import engine from '../../rules-engine/src/rules_engine/engine.py'
+
+const getPyodide = async () => {
+ // public folder:
+ return await pyodideModule.loadPyodide({
+ indexURL: 'pyodide-env/',
+ })
+}
+declare global {
+ interface Window {
+ pydd?: any
+ }
+}
+
+const runPythonScript = async () => {
+ const pyodide: any = await getPyodide()
+ // console.log(engine);
+ await pyodide.loadPackage('numpy')
+ await pyodide.runPythonAsync(engine)
+ window.pydd = pyodide as null
+
+ return pyodide
+}
+
+export const links: LinksFunction = () => {
+ return [
+ // Preload svg sprite as a resource to avoid render blocking
+ { rel: 'preload', href: iconsHref, as: 'image' },
+ // Preload CSS as a resource to avoid render blocking
+ { rel: 'preload', href: fontStyleSheetUrl, as: 'style' },
+ { rel: 'preload', href: tailwindStyleSheetUrl, as: 'style' },
+ cssBundleHref ? { rel: 'preload', href: cssBundleHref, as: 'style' } : null,
+ { rel: 'mask-icon', href: '/favicons/mask-icon.svg' },
+ {
+ rel: 'alternate icon',
+ type: 'image/png',
+ href: '/favicons/favicon-32x32.png',
+ },
+ { rel: 'apple-touch-icon', href: '/favicons/apple-touch-icon.png' },
+ {
+ rel: 'manifest',
+ href: '/site.webmanifest',
+ crossOrigin: 'use-credentials',
+ } as const, // necessary to make typescript happy
+ //These should match the css preloads above to avoid css as render blocking resource
+ { rel: 'icon', type: 'image/svg+xml', href: '/favicons/favicon.svg' },
+ { rel: 'stylesheet', href: fontStyleSheetUrl },
+ { rel: 'stylesheet', href: tailwindStyleSheetUrl },
+ cssBundleHref ? { rel: 'stylesheet', href: cssBundleHref } : null,
+ ].filter(Boolean)
+}
+
+export const meta: MetaFunction = ({ data }) => {
+ return [
+ { title: data ? 'Epic Notes' : 'Error | Epic Notes' },
+ { name: 'description', content: `Your own captain's log` },
+ ]
+}
+
+// We can only have one `loader()`. More requires special gymnastics.
+export async function loader({ request }: DataFunctionArgs) {
+ const timings = makeTimings('root loader')
+ const userId = await time(() => getUserId(request), {
+ timings,
+ type: 'getUserId',
+ desc: 'getUserId in root',
+ })
+
+ const user = userId
+ ? await time(
+ () =>
+ prisma.user.findUniqueOrThrow({
+ select: {
+ id: true,
+ name: true,
+ username: true,
+ image: { select: { id: true } },
+ roles: {
+ select: {
+ name: true,
+ permissions: {
+ select: { entity: true, action: true, access: true },
+ },
+ },
+ },
+ },
+ where: { id: userId },
+ }),
+ { timings, type: 'find user', desc: 'find user in root' },
+ )
+ : null
+ if (userId && !user) {
+ console.info('something weird happened')
+ // something weird happened... The user is authenticated but we can't find
+ // them in the database. Maybe they were deleted? Let's log them out.
+ await authenticator.logout(request, { redirectTo: '/' })
+ }
+ const { toast, headers: toastHeaders } = await getToast(request)
+ const { confettiId, headers: confettiHeaders } = getConfetti(request)
+
+ // Weather station data
+ const w_href: string =
+ 'https://archive-api.open-meteo.com/v1/archive?latitude=52.52&longitude=13.41&daily=temperature_2m_max&timezone=America%2FNew_York&start_date=2022-01-01&end_date=2023-08-30&temperature_unit=fahrenheit'
+ const w_res: Response = await fetch(w_href)
+ const weather: Weather = (await w_res.json()) as Weather
+
+ return json(
+ {
+ weather: weather,
+ user,
+ requestInfo: {
+ hints: getHints(request),
+ origin: getDomainUrl(request),
+ path: new URL(request.url).pathname,
+ userPrefs: {
+ theme: getTheme(request),
+ },
+ },
+ ENV: getEnv(),
+ toast,
+ confettiId,
+ },
+ {
+ headers: combineHeaders(
+ { 'Server-Timing': timings.toString() },
+ toastHeaders,
+ confettiHeaders,
+ ),
+ },
+ )
+}
+
+export const headers: HeadersFunction = ({ loaderHeaders }) => {
+ const headers = {
+ 'Server-Timing': loaderHeaders.get('Server-Timing') ?? '',
+ }
+ return headers
+}
+
+const ThemeFormSchema = z.object({
+ theme: z.enum(['system', 'light', 'dark']),
+})
+
+export async function action({ request }: DataFunctionArgs) {
+ const formData = await request.formData()
+ invariantResponse(
+ formData.get('intent') === 'update-theme',
+ 'Invalid intent',
+ { status: 400 },
+ )
+ const submission = parse(formData, {
+ schema: ThemeFormSchema,
+ })
+ if (submission.intent !== 'submit') {
+ return json({ status: 'success', submission } as const)
+ }
+ if (!submission.value) {
+ return json({ status: 'error', submission } as const, { status: 400 })
+ }
+ const { theme } = submission.value
+
+ const responseInit = {
+ headers: { 'set-cookie': setTheme(theme) },
+ }
+ return json({ success: true, submission }, responseInit)
+}
+
+function Document({
+ children,
+ nonce,
+ theme = 'light',
+ env = {},
+}: {
+ children: React.ReactNode
+ nonce: string
+ theme?: Theme
+ env?: Record
+}) {
+ // const [output, setOutput] = useState('(loading python...)');
+
+ // useEffect(() => {
+ // const run = async () => {
+ // const pyodide: any = await runPythonScript();
+ // // console.log(pyodide);
+ // const result = await pyodide.runPythonAsync("hdd(57, 60)");
+ // setOutput(result);
+ // };
+ // run();
+ // }, []);
+ return (
+
+
+
+
+
+
+
+
+
+
+
Output:
+ {/* {output} */}
+
+
+ {children}
+
+
+
+
+
+
+ )
+}
+
+function App() {
+ const data = useLoaderData()
+ const nonce = useNonce()
+ const user = useOptionalUser()
+ const theme = useTheme()
+ const matches = useMatches()
+ const isOnSearchPage = matches.find(m => m.id === 'routes/users+/index')
+
+ return (
+
+
+
+
+
+ )
+}
+export default withSentry(App)
+
+function UserDropdown() {
+ const user = useUser()
+ const submit = useSubmit()
+ const formRef = useRef(null)
+ return (
+
+
+
+ e.preventDefault()}
+ className="flex items-center gap-2"
+ >
+
+
+ {user.name ?? user.username}
+
+
+
+
+
+
+
+
+
+ Profile
+
+
+
+
+
+
+ Notes
+
+
+
+ {
+ event.preventDefault()
+ submit(formRef.current)
+ }}
+ >
+
+
+
+
+
+ )
+}
+
+/**
+ * @returns the user's theme preference, or the client hint theme if the user
+ * has not set a preference.
+ */
+export function useTheme() {
+ const hints = useHints()
+ // const requestInfo = useRequestInfo()
+ const optimisticMode = useOptimisticThemeMode()
+ if (optimisticMode) {
+ return optimisticMode === 'system' ? hints.theme : optimisticMode
+ }
+ // return requestInfo.userPrefs.theme ?? hints.theme
+ return hints.theme
+}
+
+/**
+ * If the user's changing their theme mode preference, this will return the
+ * value it's being changed to.
+ */
+export function useOptimisticThemeMode() {
+ const fetchers = useFetchers()
+
+ const themeFetcher = fetchers.find(
+ f => f.formData?.get('intent') === 'update-theme',
+ )
+
+ if (themeFetcher && themeFetcher.formData) {
+ const submission = parse(themeFetcher.formData, {
+ schema: ThemeFormSchema,
+ })
+ return submission.value?.theme
+ }
+}
+
+function ThemeSwitch({ userPreference }: { userPreference?: Theme | null }) {
+ const fetcher = useFetcher()
+
+ const [form] = useForm({
+ id: 'theme-switch',
+ lastSubmission: fetcher.data?.submission,
+ onValidate({ formData }) {
+ return parse(formData, { schema: ThemeFormSchema })
+ },
+ })
+
+ const optimisticMode = useOptimisticThemeMode()
+ const mode = optimisticMode ?? userPreference ?? 'system'
+ const nextMode =
+ mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
+ const modeLabel = {
+ light: (
+
+ Light
+
+ ),
+ dark: (
+
+ Dark
+
+ ),
+ system: (
+
+ System
+
+ ),
+ }
+
+ return (
+
+
+
+
+ {modeLabel[mode]}
+
+
+
+
+ )
+}
+
+export function ErrorBoundary() {
+ // the nonce doesn't rely on the loader so we can access that
+ const nonce = useNonce()
+
+ // NOTE: you cannot use useLoaderData in an ErrorBoundary because the loader
+ // likely failed to run so we have to do the best we can.
+ // We could probably do better than this (it's possible the loader did run).
+ // This would require a change in Remix.
+
+ // Just make sure your root route never errors out and you'll always be able
+ // to give the user a better UX.
+
+ return (
+
+
+
+ )
+}
diff --git a/heat-stack/package-lock.json b/heat-stack/package-lock.json
index d3b17bc2..9c9cf27e 100644
--- a/heat-stack/package-lock.json
+++ b/heat-stack/package-lock.json
@@ -50,12 +50,14 @@
"isbot": "^3.6.13",
"litefs-js": "^1.1.2",
"lru-cache": "^10.0.1",
+ "lucide-react": "^0.292.0",
"morgan": "^1.10.0",
"prisma": "^5.3.1",
"pyodide": "0.24.1",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "recharts": "^2.9.2",
"remix-auth": "^3.5.1",
"remix-auth-form": "^1.3.0",
"remix-auth-github": "^1.6.0",
@@ -4998,6 +5000,60 @@
"integrity": "sha512-DBpRoJGKJZn7RY92dPrgoMew8xCWc2P71beqsjyhEI/Ds9mOyVmBwtekyfhpwFIVt1WrxTonFifiOZ62V8CnNA==",
"dev": true
},
+ "node_modules/@types/d3-array": {
+ "version": "3.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz",
+ "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA=="
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.2.tgz",
+ "integrity": "sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g=="
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg=="
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.3.tgz",
+ "integrity": "sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.1.tgz",
+ "integrity": "sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA=="
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.6.tgz",
+ "integrity": "sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.4.tgz",
+ "integrity": "sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.2.tgz",
+ "integrity": "sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg=="
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg=="
+ },
"node_modules/@types/debug": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz",
@@ -7219,6 +7275,11 @@
"url": "https://joebell.co.uk"
}
},
+ "node_modules/classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"node_modules/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -7690,6 +7751,116 @@
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"dev": true
},
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+ "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -7763,6 +7934,11 @@
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"dev": true
},
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+ },
"node_modules/decode-named-character-reference": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@@ -8131,6 +8307,14 @@
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true
},
+ "node_modules/dom-helpers": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
+ "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
+ "dependencies": {
+ "@babel/runtime": "^7.1.2"
+ }
+ },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
@@ -9594,6 +9778,11 @@
"node": ">=6"
}
},
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+ },
"node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
@@ -9761,6 +9950,14 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
+ "node_modules/fast-equals": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
+ "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -10881,6 +11078,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/intl-parse-accept-language": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/intl-parse-accept-language/-/intl-parse-accept-language-1.0.0.tgz",
@@ -12004,8 +12209,7 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
@@ -12133,6 +12337,14 @@
"node": "14 || >=16.14"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.292.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.292.0.tgz",
+ "integrity": "sha512-rRgUkpEHWpa5VCT66YscInCQmQuPCB1RFRzkkxMxg4b+jaL0V12E3riWWR2Sh5OIiUhCwGW/ZExuEO4Az32E6Q==",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -15325,7 +15537,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -15761,6 +15972,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
+ },
"node_modules/react-property": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz",
@@ -15820,6 +16036,18 @@
}
}
},
+ "node_modules/react-resize-detector": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
+ "integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-router": {
"version": "6.16.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
@@ -15850,6 +16078,20 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-smooth": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.5.tgz",
+ "integrity": "sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==",
+ "dependencies": {
+ "fast-equals": "^5.0.0",
+ "react-transition-group": "2.9.0"
+ },
+ "peerDependencies": {
+ "prop-types": "^15.6.0",
+ "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@@ -15872,6 +16114,21 @@
}
}
},
+ "node_modules/react-transition-group": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
+ "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
+ "dependencies": {
+ "dom-helpers": "^3.4.0",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0",
+ "react-dom": ">=15.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -15965,6 +16222,38 @@
"node": ">=8.10.0"
}
},
+ "node_modules/recharts": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.9.2.tgz",
+ "integrity": "sha512-ig0zYgO5nUP/896GW16b9yy2sHIRW1AHB90x48hypFTSjjxQt/J9rPzlLJjgNupzJKEHPCwMi1VnvN/k20K45w==",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.19",
+ "react-is": "^16.10.2",
+ "react-resize-detector": "^8.0.4",
+ "react-smooth": "^2.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "prop-types": "^15.6.0",
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -17839,6 +18128,11 @@
"safe-buffer": "~5.1.0"
}
},
+ "node_modules/tiny-invariant": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
+ },
"node_modules/tinybench": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz",
@@ -18811,6 +19105,27 @@
"node": ">=4"
}
},
+ "node_modules/victory-vendor": {
+ "version": "36.6.12",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.12.tgz",
+ "integrity": "sha512-pJrTkNHln+D83vDCCSUf0ZfxBvIaVrFHmrBOsnnLAbdqfudRACAj51He2zU94/IWq9464oTADcPVkmWAfNMwgA==",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
"node_modules/vite": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
diff --git a/heat-stack/package.json b/heat-stack/package.json
index 28f48d81..d150f979 100644
--- a/heat-stack/package.json
+++ b/heat-stack/package.json
@@ -81,12 +81,14 @@
"isbot": "^3.6.13",
"litefs-js": "^1.1.2",
"lru-cache": "^10.0.1",
+ "lucide-react": "^0.292.0",
"morgan": "^1.10.0",
"prisma": "^5.3.1",
"pyodide": "0.24.1",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "recharts": "^2.9.2",
"remix-auth": "^3.5.1",
"remix-auth-form": "^1.3.0",
"remix-auth-github": "^1.6.0",