feat: new dashboards logic, closes #17
This commit is contained in:
parent
d92f18db3f
commit
986f7ab888
17
src/pages/_middleware.ts
Normal file
17
src/pages/_middleware.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function middleware(req: NextRequest) {
|
||||||
|
const { pathname, searchParams } = req.nextUrl;
|
||||||
|
|
||||||
|
console.log(pathname);
|
||||||
|
if (pathname === "/dashboards") {
|
||||||
|
const dashboardId = searchParams.get("dashboardId");
|
||||||
|
if (dashboardId) {
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL(`/dashboards/view/${dashboardId}`, req.url)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
|
@ -1,211 +0,0 @@
|
||||||
/* Imports */
|
|
||||||
import axios from "axios";
|
|
||||||
import { GetServerSideProps, NextPage } from "next";
|
|
||||||
import { useRouter } from "next/router"; // https://nextjs.org/docs/api-reference/next/router
|
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
import { DashboardItem } from "../backend/dashboards";
|
|
||||||
import { getPlatformsConfig, PlatformConfig } from "../backend/platforms";
|
|
||||||
import { DashboardCreator } from "../web/display/DashboardCreator";
|
|
||||||
import { DisplayForecasts } from "../web/display/DisplayForecasts";
|
|
||||||
import { Layout } from "../web/display/Layout";
|
|
||||||
import { addLabelsToForecasts, FrontendForecast } from "../web/platforms";
|
|
||||||
import { getDashboardForecastsByDashboardId } from "../web/worker/getDashboardForecasts";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
initialDashboardForecasts: FrontendForecast[];
|
|
||||||
initialDashboardId: string | null;
|
|
||||||
initialDashboardItem: DashboardItem | null;
|
|
||||||
platformsConfig: PlatformConfig[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<Props> = async (
|
|
||||||
context
|
|
||||||
) => {
|
|
||||||
const dashboardIdQ = context.query.dashboardId;
|
|
||||||
const dashboardId: string | undefined =
|
|
||||||
typeof dashboardIdQ === "object" ? dashboardIdQ[0] : dashboardIdQ;
|
|
||||||
|
|
||||||
const platformsConfig = getPlatformsConfig({ withGuesstimate: false });
|
|
||||||
|
|
||||||
if (!dashboardId) {
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
platformsConfig,
|
|
||||||
initialDashboardForecasts: [],
|
|
||||||
initialDashboardId: null,
|
|
||||||
initialDashboardItem: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { dashboardForecasts, dashboardItem } =
|
|
||||||
await getDashboardForecastsByDashboardId({
|
|
||||||
dashboardId,
|
|
||||||
});
|
|
||||||
const frontendDashboardForecasts = addLabelsToForecasts(
|
|
||||||
dashboardForecasts,
|
|
||||||
platformsConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
initialDashboardForecasts: frontendDashboardForecasts,
|
|
||||||
initialDashboardId: dashboardId,
|
|
||||||
initialDashboardItem: dashboardItem,
|
|
||||||
platformsConfig,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
const DashboardsPage: NextPage<Props> = ({
|
|
||||||
initialDashboardForecasts,
|
|
||||||
initialDashboardItem,
|
|
||||||
platformsConfig,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
|
||||||
const [dashboardForecasts, setDashboardForecasts] = useState(
|
|
||||||
initialDashboardForecasts
|
|
||||||
);
|
|
||||||
const [dashboardItem, setDashboardItem] = useState(initialDashboardItem);
|
|
||||||
|
|
||||||
const handleSubmit = async (data) => {
|
|
||||||
console.log(data);
|
|
||||||
// Send to server to create
|
|
||||||
// Get back the id
|
|
||||||
let response = await axios({
|
|
||||||
url: "/api/create-dashboard-from-ids",
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
}).then((res) => res.data);
|
|
||||||
let dashboardId = response.dashboardId;
|
|
||||||
if (!!dashboardId) {
|
|
||||||
console.log("response: ", response);
|
|
||||||
if (typeof window !== "undefined") {
|
|
||||||
let urlWithoutDefaultParameters = `/dashboards?dashboardId=${dashboardId}`;
|
|
||||||
if (!window.location.href.includes(urlWithoutDefaultParameters)) {
|
|
||||||
window.history.replaceState(
|
|
||||||
null,
|
|
||||||
"Metaforecast",
|
|
||||||
urlWithoutDefaultParameters
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// router.push(`?dashboardId=${dashboardId}`)
|
|
||||||
// display it
|
|
||||||
|
|
||||||
let { dashboardForecasts, dashboardItem } =
|
|
||||||
await getDashboardForecastsByDashboardId({
|
|
||||||
dashboardId,
|
|
||||||
});
|
|
||||||
setDashboardForecasts(
|
|
||||||
addLabelsToForecasts(dashboardForecasts, platformsConfig)
|
|
||||||
);
|
|
||||||
setDashboardItem(dashboardItem);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let isGraubardEasterEgg = (name: string) =>
|
|
||||||
name == "Clay Graubard" ? true : false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout page="dashboard">
|
|
||||||
{/* Display forecasts */}
|
|
||||||
<div className="mt-7 mb-7">
|
|
||||||
<h1
|
|
||||||
className={
|
|
||||||
!!dashboardItem && !!dashboardItem.title
|
|
||||||
? "text-4xl text-center text-gray-600 mt-2 mb-2"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!!dashboardItem ? dashboardItem.title : ""}
|
|
||||||
</h1>
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
!!dashboardItem &&
|
|
||||||
!!dashboardItem.creator &&
|
|
||||||
!isGraubardEasterEgg(dashboardItem.creator)
|
|
||||||
? "text-lg text-center text-gray-600 mt-2 mb-2"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!!dashboardItem ? `Created by: ${dashboardItem.creator}` : ""}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
!!dashboardItem &&
|
|
||||||
!!dashboardItem.creator &&
|
|
||||||
isGraubardEasterEgg(dashboardItem.creator)
|
|
||||||
? "text-lg text-center text-gray-600 mt-2 mb-2"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!!dashboardItem ? `Created by: @` : ""}
|
|
||||||
<a
|
|
||||||
href={"https://twitter.com/ClayGraubard"}
|
|
||||||
className="text-blue-600"
|
|
||||||
>
|
|
||||||
Clay Graubard
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className={
|
|
||||||
!!dashboardItem && !!dashboardItem.description
|
|
||||||
? "text-lg text-center text-gray-600 mt-2 mb-2"
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{!!dashboardItem ? `${dashboardItem.description}` : ""}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
|
||||||
<DisplayForecasts
|
|
||||||
results={dashboardForecasts}
|
|
||||||
numDisplay={dashboardForecasts.length}
|
|
||||||
showIdToggle={false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* */}
|
|
||||||
<h3 className="flex items-center col-start-2 col-end-2 w-full justify-center mt-8 mb-4">
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
className="flex-grow bg-gray-300 rounded h-0.5"
|
|
||||||
></span>
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
!!dashboardForecasts && dashboardForecasts.length > 0
|
|
||||||
? `mx-3 text-md font-medium text-center`
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Or create your own
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
!dashboardForecasts || dashboardForecasts.length == 0
|
|
||||||
? `mx-3 text-md font-medium text-center`
|
|
||||||
: "hidden"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Create a dashboard!
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
className="flex-grow bg-gray-300 rounded h-0.5"
|
|
||||||
></span>
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-3 justify-center">
|
|
||||||
<div className="flex col-start-2 col-end-2 items-center justify-center">
|
|
||||||
<DashboardCreator handleSubmit={handleSubmit} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DashboardsPage;
|
|
88
src/pages/dashboards/index.tsx
Normal file
88
src/pages/dashboards/index.tsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import axios from "axios";
|
||||||
|
import { GetServerSideProps, NextPage } from "next";
|
||||||
|
import { useRouter } from "next/router"; // https://nextjs.org/docs/api-reference/next/router
|
||||||
|
|
||||||
|
import { DashboardItem } from "../../backend/dashboards";
|
||||||
|
import { getPlatformsConfig, PlatformConfig } from "../../backend/platforms";
|
||||||
|
import { DashboardCreator } from "../../web/display/DashboardCreator";
|
||||||
|
import { Layout } from "../../web/display/Layout";
|
||||||
|
import { LineHeader } from "../../web/display/LineHeader";
|
||||||
|
import { addLabelsToForecasts, FrontendForecast } from "../../web/platforms";
|
||||||
|
import { getDashboardForecastsByDashboardId } from "../../web/worker/getDashboardForecasts";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
initialDashboardForecasts: FrontendForecast[];
|
||||||
|
initialDashboardId: string | null;
|
||||||
|
initialDashboardItem: DashboardItem | null;
|
||||||
|
platformsConfig: PlatformConfig[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const dashboardIdQ = context.query.dashboardId;
|
||||||
|
const dashboardId: string | undefined =
|
||||||
|
typeof dashboardIdQ === "object" ? dashboardIdQ[0] : dashboardIdQ;
|
||||||
|
|
||||||
|
const platformsConfig = getPlatformsConfig({ withGuesstimate: false });
|
||||||
|
|
||||||
|
if (!dashboardId) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
platformsConfig,
|
||||||
|
initialDashboardForecasts: [],
|
||||||
|
initialDashboardId: null,
|
||||||
|
initialDashboardItem: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dashboardForecasts, dashboardItem } =
|
||||||
|
await getDashboardForecastsByDashboardId({
|
||||||
|
dashboardId,
|
||||||
|
});
|
||||||
|
const frontendDashboardForecasts = addLabelsToForecasts(
|
||||||
|
dashboardForecasts,
|
||||||
|
platformsConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
initialDashboardForecasts: frontendDashboardForecasts,
|
||||||
|
initialDashboardId: dashboardId,
|
||||||
|
initialDashboardItem: dashboardItem,
|
||||||
|
platformsConfig,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
const DashboardsPage: NextPage<Props> = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubmit = async (data) => {
|
||||||
|
// Send to server to create
|
||||||
|
// Get back the id
|
||||||
|
let response = await axios({
|
||||||
|
url: "/api/create-dashboard-from-ids",
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
}).then((res) => res.data);
|
||||||
|
await router.push(`/dashboards/view/${response.dashboardId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout page="dashboard">
|
||||||
|
<div className="flex flex-col my-8 space-y-8">
|
||||||
|
<LineHeader>Create a dashboard!</LineHeader>
|
||||||
|
|
||||||
|
<div className="self-center">
|
||||||
|
<DashboardCreator handleSubmit={handleSubmit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardsPage;
|
118
src/pages/dashboards/view/[id].tsx
Normal file
118
src/pages/dashboards/view/[id].tsx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import { GetServerSideProps, NextPage } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { DashboardItem } from "../../../backend/dashboards";
|
||||||
|
import { getPlatformsConfig } from "../../../backend/platforms";
|
||||||
|
import { DisplayForecasts } from "../../../web/display/DisplayForecasts";
|
||||||
|
import { InfoBox } from "../../../web/display/InfoBox";
|
||||||
|
import { Layout } from "../../../web/display/Layout";
|
||||||
|
import { LineHeader } from "../../../web/display/LineHeader";
|
||||||
|
import { addLabelsToForecasts, FrontendForecast } from "../../../web/platforms";
|
||||||
|
import { getDashboardForecastsByDashboardId } from "../../../web/worker/getDashboardForecasts";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dashboardForecasts: FrontendForecast[];
|
||||||
|
dashboardId: string;
|
||||||
|
dashboardItem: DashboardItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const dashboardId = context.query.id as string;
|
||||||
|
|
||||||
|
const platformsConfig = getPlatformsConfig({ withGuesstimate: false });
|
||||||
|
|
||||||
|
const { dashboardForecasts, dashboardItem } =
|
||||||
|
await getDashboardForecastsByDashboardId({
|
||||||
|
dashboardId,
|
||||||
|
});
|
||||||
|
const frontendDashboardForecasts = addLabelsToForecasts(
|
||||||
|
dashboardForecasts,
|
||||||
|
platformsConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
dashboardForecasts: frontendDashboardForecasts,
|
||||||
|
dashboardId,
|
||||||
|
dashboardItem,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const DashboardMetadata: React.FC<{ dashboardItem: DashboardItem }> = ({
|
||||||
|
dashboardItem,
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
{dashboardItem?.title ? (
|
||||||
|
<h1 className="text-4xl text-center text-gray-600 mt-2 mb-2">
|
||||||
|
{dashboardItem.title}
|
||||||
|
</h1>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{dashboardItem && dashboardItem.creator ? (
|
||||||
|
<p className="text-lg text-center text-gray-600 mt-2 mb-2">
|
||||||
|
Created by:{" "}
|
||||||
|
{dashboardItem.creator === "Clay Graubard" ? (
|
||||||
|
<>
|
||||||
|
@
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/ClayGraubard"
|
||||||
|
className="text-blue-600"
|
||||||
|
>
|
||||||
|
Clay Graubard
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
dashboardItem.creator
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{dashboardItem?.description ? (
|
||||||
|
<p className="text-lg text-center text-gray-600 mt-2 mb-2">
|
||||||
|
{dashboardItem.description}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
const ViewDashboardPage: NextPage<Props> = ({
|
||||||
|
dashboardForecasts,
|
||||||
|
dashboardItem,
|
||||||
|
dashboardId,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Layout page="view-dashboard">
|
||||||
|
<div className="flex flex-col my-8 space-y-8">
|
||||||
|
{dashboardItem ? (
|
||||||
|
<DashboardMetadata dashboardItem={dashboardItem} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
<DisplayForecasts
|
||||||
|
results={dashboardForecasts}
|
||||||
|
numDisplay={dashboardForecasts.length}
|
||||||
|
showIdToggle={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-xl self-center">
|
||||||
|
<InfoBox>
|
||||||
|
Dashboards cannot be changed after they are created.
|
||||||
|
</InfoBox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LineHeader>
|
||||||
|
<Link href="/dashboards" passHref>
|
||||||
|
<a>Create your own dashboard</a>
|
||||||
|
</Link>
|
||||||
|
</LineHeader>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewDashboardPage;
|
10
src/web/display/Button.tsx
Normal file
10
src/web/display/Button.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
|
||||||
|
|
||||||
|
export const Button: React.FC<Props> = ({ children, ...rest }) => (
|
||||||
|
<button
|
||||||
|
{...rest}
|
||||||
|
className="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded text-center"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
5
src/web/display/InfoBox.tsx
Normal file
5
src/web/display/InfoBox.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const InfoBox: React.FC = ({ children }) => (
|
||||||
|
<p className="bg-gray-200 text-gray-700 py-2 px-4 border border-transparent text-center">
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
7
src/web/display/LineHeader.tsx
Normal file
7
src/web/display/LineHeader.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const LineHeader: React.FC = ({ children }) => (
|
||||||
|
<h3 className="flex items-center justify-center w-full">
|
||||||
|
<span aria-hidden="true" className="flex-grow bg-gray-300 rounded h-0.5" />
|
||||||
|
<span className="mx-3 text-md font-medium text-center">{children}</span>
|
||||||
|
<span aria-hidden="true" className="flex-grow bg-gray-300 rounded h-0.5" />
|
||||||
|
</h3>
|
||||||
|
);
|
|
@ -1,4 +1,7 @@
|
||||||
import React, { useState } from "react";
|
import React, { EventHandler, SyntheticEvent, useState } from "react";
|
||||||
|
|
||||||
|
import { Button } from "./Button";
|
||||||
|
import { InfoBox } from "./InfoBox";
|
||||||
|
|
||||||
const exampleInput = `{
|
const exampleInput = `{
|
||||||
"title": "Random example",
|
"title": "Random example",
|
||||||
|
@ -13,32 +16,27 @@ interface Props {
|
||||||
|
|
||||||
export const DashboardCreator: React.FC<Props> = ({ handleSubmit }) => {
|
export const DashboardCreator: React.FC<Props> = ({ handleSubmit }) => {
|
||||||
const [value, setValue] = useState(exampleInput);
|
const [value, setValue] = useState(exampleInput);
|
||||||
const [displayingDoneMessage, setDisplayingDoneMessage] = useState(false);
|
const [acting, setActing] = useState(false);
|
||||||
const [displayingDoneMessageTimer, setDisplayingDoneMessageTimer] =
|
|
||||||
useState(null);
|
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event) => {
|
||||||
setValue(event.target.value);
|
setValue(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitInner = (event) => {
|
const handleSubmitInner: EventHandler<SyntheticEvent> = async (event) => {
|
||||||
clearTimeout(displayingDoneMessageTimer);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
console.log(value);
|
|
||||||
try {
|
try {
|
||||||
let newData = JSON.parse(value);
|
const newData = JSON.parse(value);
|
||||||
|
|
||||||
if (!newData || !newData.ids || newData.ids.length == 0) {
|
if (!newData || !newData.ids || newData.ids.length == 0) {
|
||||||
throw Error("Not enough objects");
|
throw Error("Not enough objects");
|
||||||
} else {
|
} else {
|
||||||
handleSubmit(newData);
|
setActing(true);
|
||||||
setDisplayingDoneMessage(true);
|
await handleSubmit(newData);
|
||||||
const timer = setTimeout(() => setDisplayingDoneMessage(false), 3000);
|
setActing(false);
|
||||||
setDisplayingDoneMessageTimer(timer);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDisplayingDoneMessage(false);
|
setActing(false);
|
||||||
const substituteText = `Error: ${error.message}
|
const substituteText = `Error: ${error.message}
|
||||||
|
|
||||||
Try something like:
|
Try something like:
|
||||||
|
@ -50,36 +48,21 @@ Your old input was: ${value}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmitInner} className="block place-centers">
|
<form onSubmit={handleSubmitInner}>
|
||||||
<textarea
|
<div className="flex flex-col items-center space-y-5 max-w-2xl">
|
||||||
value={value}
|
<textarea value={value} onChange={handleChange} rows={8} cols={50} />
|
||||||
onChange={handleChange}
|
<Button
|
||||||
rows={8}
|
disabled={acting}
|
||||||
cols={50}
|
onClick={acting ? undefined : handleSubmitInner}
|
||||||
className=""
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<div className="grid grid-cols-3 text-center">
|
|
||||||
<button
|
|
||||||
className="block col-start-2 col-end-2 w-full bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded mt-5 p-10 text-center"
|
|
||||||
onClick={handleSubmitInner}
|
|
||||||
>
|
>
|
||||||
Create dashboard
|
{acting ? "Creating..." : "Create dashboard"}
|
||||||
</button>
|
</Button>
|
||||||
<button
|
|
||||||
className={
|
<InfoBox>
|
||||||
displayingDoneMessage
|
|
||||||
? "block col-start-2 col-end-2 bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-2 border border-blue-500 hover:border-transparent rounded mt-2 p-2 text-center "
|
|
||||||
: "hidden "
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Done!
|
|
||||||
</button>
|
|
||||||
<p className="block col-start-1 col-end-4 bg-gray-200 text-gray-700 py-2 px-4 border border-transparent mt-5 p-10 text-center mb-6">
|
|
||||||
You can find the necessary ids by toggling the advanced options in the
|
You can find the necessary ids by toggling the advanced options in the
|
||||||
search, or by visiting{" "}
|
search, or by visiting{" "}
|
||||||
<a href="/api/all-forecasts">/api/all-forecasts</a>
|
<a href="/api/all-forecasts">/api/all-forecasts</a>
|
||||||
</p>
|
</InfoBox>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { DisplayForecasts } from "./DisplayForecasts";
|
import { DisplayForecasts } from "./DisplayForecasts";
|
||||||
import displayOneForecast from "./displayOneForecastForCapture";
|
import { DisplayOneForecastForCapture } from "./DisplayOneForecastForCapture";
|
||||||
|
|
||||||
export function displayForecastsWrapperForSearch({
|
export function displayForecastsWrapperForSearch({
|
||||||
results,
|
results,
|
||||||
|
@ -23,9 +23,9 @@ export function displayForecastsWrapperForCapture({
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 w-full justify-center">
|
<div className="grid grid-cols-1 w-full justify-center">
|
||||||
{displayOneForecast({
|
<DisplayOneForecastForCapture
|
||||||
result: results[whichResultToDisplayAndCapture],
|
result={results[whichResultToDisplayAndCapture]}
|
||||||
})}
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ interface Props {
|
||||||
result: FrontendForecast;
|
result: FrontendForecast;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DisplayOneForecast: React.FC<Props> = ({ result }) => {
|
export const DisplayOneForecastForCapture: React.FC<Props> = ({ result }) => {
|
||||||
const [hasDisplayBeenCaptured, setHasDisplayBeenCaptured] = useState(false);
|
const [hasDisplayBeenCaptured, setHasDisplayBeenCaptured] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -248,8 +248,6 @@ const DisplayOneForecast: React.FC<Props> = ({ result }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DisplayOneForecast;
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard
|
// https://stackoverflow.com/questions/39501289/in-reactjs-how-to-copy-text-to-clipboard
|
||||||
// Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
|
// Note: https://stackoverflow.com/questions/66016033/can-no-longer-upload-images-to-imgur-from-localhost
|
||||||
// Use: http://imgurtester:3000/embed for testing.
|
// Use: http://imgurtester:3000/embed for testing.
|
||||||
|
|
15
src/web/hooks.ts
Normal file
15
src/web/hooks.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import React, { DependencyList, EffectCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
export const useNoInitialEffect = (
|
||||||
|
effect: EffectCallback,
|
||||||
|
deps: DependencyList
|
||||||
|
) => {
|
||||||
|
const initial = React.useRef(true);
|
||||||
|
useEffect(() => {
|
||||||
|
if (initial.current) {
|
||||||
|
initial.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return effect();
|
||||||
|
}, deps);
|
||||||
|
};
|
|
@ -1,25 +1,15 @@
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { DependencyList, EffectCallback, Fragment, useEffect, useState } from "react";
|
import React, { Fragment, useState } from "react";
|
||||||
|
|
||||||
import { ButtonsForStars } from "../display/ButtonsForStars";
|
import { ButtonsForStars } from "../display/ButtonsForStars";
|
||||||
import { MultiSelectPlatform } from "../display/MultiSelectPlatform";
|
import { MultiSelectPlatform } from "../display/MultiSelectPlatform";
|
||||||
import { QueryForm } from "../display/QueryForm";
|
import { QueryForm } from "../display/QueryForm";
|
||||||
import { SliderElement } from "../display/SliderElement";
|
import { SliderElement } from "../display/SliderElement";
|
||||||
|
import { useNoInitialEffect } from "../hooks";
|
||||||
import { FrontendForecast } from "../platforms";
|
import { FrontendForecast } from "../platforms";
|
||||||
import searchAccordingToQueryData from "../worker/searchAccordingToQueryData";
|
import searchAccordingToQueryData from "../worker/searchAccordingToQueryData";
|
||||||
import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage";
|
import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage";
|
||||||
|
|
||||||
const useNoInitialEffect = (effect: EffectCallback, deps: DependencyList) => {
|
|
||||||
const initial = React.useRef(true);
|
|
||||||
useEffect(() => {
|
|
||||||
if (initial.current) {
|
|
||||||
initial.current = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return effect();
|
|
||||||
}, deps);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props extends AnySearchPageProps {
|
interface Props extends AnySearchPageProps {
|
||||||
hasSearchbar: boolean;
|
hasSearchbar: boolean;
|
||||||
hasCapture: boolean;
|
hasCapture: boolean;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user