Skip to content

Commit cbb5b17

Browse files
authored
Merge pull request #399 from wri/develop
Merging in changes from develop
2 parents c009ee6 + a83c6d3 commit cbb5b17

File tree

13 files changed

+996
-896
lines changed

13 files changed

+996
-896
lines changed

app/(home)/sections/11_CTA.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function CTASection() {
3737
</Box>
3838
<Button asChild variant="solid" colorPalette="primary" rounded="lg">
3939
<Link href="/app">
40-
Try the preview
40+
{LANDING_PAGE_VERSION === "public" ? "Explore the beta" : "Try the preview"}
4141
<CaretRightIcon weight="bold" />
4242
</Link>
4343
</Button>

app/(home)/sections/1_Hero.tsx

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import {
66
Container,
77
Flex,
88
Heading,
9-
Input,
109
Text,
1110
Badge,
11+
Textarea,
12+
Spinner,
1213
} from "@chakra-ui/react";
1314
import {
1415
ArrowsClockwiseIcon,
1516
CaretRightIcon,
1617
QuestionIcon,
1718
} from "@phosphor-icons/react";
1819
import { Tooltip } from "@/components/ui/tooltip";
19-
import useChatStore from "@/app/store/chatStore";
2020
import GlobalHeader from "../../components/GlobalHeader";
2121

2222
type PromptMarqueeProps = {
@@ -36,14 +36,19 @@ export default function LandingHero({
3636
const [inputValue, setInputValue] = useState("");
3737
const [isInputFocused, setIsInputFocused] = useState(false);
3838
const [ready, setReady] = useState(false);
39-
const { sendMessage, isLoading } = useChatStore();
39+
const [sendingPrompt, setSendingPrompt] = useState(false);
4040

4141
useEffect(() => {
4242
setReady(true);
4343
}, []);
4444

4545
useEffect(() => {
46-
if (isInputFocused || inputValue.length > 0) return; // Pause timer if typing
46+
setSendingPrompt(false);
47+
}, []);
48+
49+
useEffect(() => {
50+
if (!ready || isInputFocused || inputValue.length > 0 || sendingPrompt)
51+
return; // Pause timer if typing or sendingPrompt
4752
const interval = setInterval(() => {
4853
setPromptTimer((prev) => {
4954
if (prev > 1) {
@@ -57,17 +62,36 @@ export default function LandingHero({
5762
});
5863
}, 1000);
5964
return () => clearInterval(interval);
60-
}, [prompts.length, setPromptIndex, isInputFocused, inputValue]);
65+
}, [
66+
prompts.length,
67+
setPromptIndex,
68+
isInputFocused,
69+
inputValue,
70+
sendingPrompt,
71+
]);
6172
// Measure the width of one set of prompts after render
6273

6374
const LANDING_PAGE_VERSION = process.env.NEXT_PUBLIC_LANDING_PAGE_VERSION;
6475

76+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
77+
// Submit on Enter (without Shift) or Command+Enter
78+
if (
79+
(e.key === "Enter" && !e.shiftKey && !e.metaKey) ||
80+
(e.key === "Enter" && e.metaKey)
81+
) {
82+
e.preventDefault(); // Prevents newline
83+
submitPrompt();
84+
}
85+
// If Shift+Enter, do nothing: allow newline
86+
};
87+
6588
const submitPrompt = async () => {
66-
if (isLoading) return;
89+
if (sendingPrompt) return;
90+
setSendingPrompt(true);
6791
const message = inputValue.trim() || prompts[promptIndex];
6892
localStorage.setItem("bypassWelcomeModal", "true");
69-
await sendMessage(message);
70-
router.push("/app");
93+
const encodedMessage = encodeURI(message);
94+
router.push(`/app?prompt=${encodedMessage}`);
7195
};
7296

7397
return (
@@ -155,7 +179,7 @@ export default function LandingHero({
155179
{(LANDING_PAGE_VERSION === "public" ||
156180
LANDING_PAGE_VERSION === "limited") && (
157181
<Box rounded="xl" bg="bg" p="4" zIndex="10">
158-
<Input
182+
<Textarea
159183
key={
160184
!isInputFocused && inputValue === ""
161185
? promptIndex
@@ -165,6 +189,7 @@ export default function LandingHero({
165189
onChange={(e) => setInputValue(e.target.value)}
166190
onFocus={() => setIsInputFocused(true)}
167191
onBlur={() => setIsInputFocused(false)}
192+
onKeyDown={handleKeyDown}
168193
p="0"
169194
outline="none"
170195
borderWidth="0"
@@ -176,6 +201,16 @@ export default function LandingHero({
176201
_focusWithin={{
177202
animationPlayState: "paused",
178203
}}
204+
aria-label="Ask a question..."
205+
minH="20px"
206+
autoresize
207+
maxH="3lh"
208+
border="none"
209+
color={sendingPrompt ? "fg.subtle" : "fg"}
210+
_placeholder={{ color: sendingPrompt ? "fg.subtle" : "fg" }}
211+
disabled={sendingPrompt}
212+
_disabled={{ opacity: 1 }}
213+
_focus={{ outline: "none", boxShadow: "none" }}
179214
/>
180215
<Flex justifyContent="space-between" mt="4" gap={4}>
181216
<Flex gap="2" alignItems="flex-start" flexDirection="column">
@@ -196,7 +231,9 @@ export default function LandingHero({
196231
bg: "secondary.100",
197232
animation: ready ? "fillWidth" : "none",
198233
animationPlayState:
199-
isInputFocused || inputValue.length > 0
234+
isInputFocused ||
235+
inputValue.length > 0 ||
236+
sendingPrompt
200237
? "paused"
201238
: "running",
202239
}}
@@ -219,9 +256,10 @@ export default function LandingHero({
219256
rounded="lg"
220257
onClick={submitPrompt}
221258
title="Submit prompt to assistant and go to application"
222-
disabled={isLoading}
259+
disabled={sendingPrompt}
223260
>
224-
Go
261+
{sendingPrompt ? <Spinner size="sm" mr={2} /> : null}
262+
{sendingPrompt ? "Sending" : "Go"}
225263
<CaretRightIcon weight="bold" />
226264
</Button>
227265
</Flex>

app/ChatPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function ChatPanel() {
5959
}, [width]);
6060

6161
return (
62-
<Flex minH="100%" maxH="100%" gridArea="chat" zIndex={1100} position="relative" bg="bg">
62+
<Flex minH="100%" maxH="100%" gridArea="chat" zIndex={1000} position="relative" bg="bg">
6363
<Flex
6464
minH="100%"
6565
maxH="100%"

app/actions/ortto.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use server";
2+
3+
import { headers } from "next/headers";
4+
5+
type OrttoPayload = {
6+
email: string;
7+
firstName: string;
8+
lastName: string;
9+
sector: string;
10+
jobTitle: string;
11+
companyOrganization: string;
12+
countryCode: string;
13+
Topics: string[];
14+
receiveNewsEmails: boolean;
15+
ipAddr?: string;
16+
};
17+
18+
export async function submitToOrtto(data: Omit<OrttoPayload, "ipAddr">) {
19+
try {
20+
const headersList = await headers();
21+
const ipAddr =
22+
headersList.get("x-forwarded-for")?.split(",")[0]?.trim() ||
23+
headersList.get("x-real-ip") ||
24+
"unknown";
25+
26+
const payload: OrttoPayload = {
27+
...data,
28+
ipAddr,
29+
};
30+
31+
console.log("Submitting to Ortto:", JSON.stringify(payload, null, 2));
32+
33+
const response = await fetch("https://ortto.wri.org/custom-forms/gnw/", {
34+
method: "POST",
35+
headers: {
36+
"Content-Type": "application/json",
37+
},
38+
body: JSON.stringify(payload),
39+
});
40+
41+
if (!response.ok) {
42+
const text = await response.text();
43+
console.error(
44+
`Ortto submission failed: ${response.status} ${response.statusText}`,
45+
text
46+
);
47+
// We don't throw here to avoid blocking the main flow if Ortto is down,
48+
// but we log it.
49+
return { success: false, error: response.statusText };
50+
}
51+
52+
return { success: true };
53+
} catch (error) {
54+
console.error("Error submitting to Ortto:", error);
55+
return { success: false, error: "Internal Server Error" };
56+
}
57+
}
58+

app/app/(chat)/page.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,56 @@
11
"use client";
22

3-
import { useEffect } from "react";
3+
import { useEffect, useState, Suspense } from "react";
4+
import { Loader } from "@chakra-ui/react";
5+
import { useRouter, useSearchParams } from "next/navigation";
46
import useChatStore from "@/app/store/chatStore";
57
import useContextStore from "@/app/store/contextStore";
68
import useMapStore from "@/app/store/mapStore";
79

8-
export default function Home() {
9-
const { reset: resetChatStore } = useChatStore();
10+
function NewThread() {
11+
const {
12+
reset: resetChatStore,
13+
sendMessage,
14+
currentThreadId,
15+
} = useChatStore();
1016
const { reset: resetContextStore } = useContextStore();
1117
const { reset: resetMapStore } = useMapStore();
18+
const searchParams = useSearchParams();
19+
const [hasMounted, setHasMounted] = useState(false);
20+
const router = useRouter();
1221

1322
useEffect(() => {
1423
resetChatStore();
1524
resetMapStore();
1625
resetContextStore();
1726
}, [resetChatStore, resetContextStore, resetMapStore]);
1827

19-
return null; // The layout handles all the UI
28+
useEffect(() => {
29+
setHasMounted(true);
30+
}, []);
31+
32+
const submitPrompt = async (prompt: string) => {
33+
const result = await sendMessage(prompt);
34+
if (result.isNew) {
35+
router.replace(`/app/threads/${result.id}`);
36+
}
37+
};
38+
39+
useEffect(() => {
40+
if (!hasMounted) return;
41+
const prompt = searchParams.get("prompt");
42+
if (prompt && !currentThreadId) {
43+
submitPrompt(prompt);
44+
}
45+
}, [hasMounted, submitPrompt, searchParams, currentThreadId]);
46+
47+
return null;
2048
}
49+
50+
export default function AppPage() {
51+
return (
52+
<Suspense fallback={<Loader />}>
53+
<NewThread />
54+
</Suspense>
55+
);
56+
}

app/components/CookieConsent.tsx

Lines changed: 0 additions & 101 deletions
This file was deleted.

app/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
44
import Script from "next/script";
55
import Analytics from "@/app/components/Analytics";
66
import HotjarTrigger from "@/app/components/HotjarTrigger";
7+
import "./theme/cookies.css"
78

89
const ibmPlexSans = IBM_Plex_Sans({
910
variable: "--font-IBMPlexSans",

0 commit comments

Comments
 (0)