Skip to content

Commit f97287b

Browse files
sshaderConvex, Inc.
authored andcommitted
[local dashboard] toast to encourage signing up for Convex (#35931)
Changes on top of [this PR](get-convex/convex#35900) to make it a little less colorful and to update the wording (also "overlay" to me implies that it takes up the full screen, so I called this a "toast" instead) GitOrigin-RevId: 0156c5b20fbc4d3a079f62bb990f9ced49b2891b
1 parent f345a96 commit f97287b

File tree

5 files changed

+182
-28
lines changed

5 files changed

+182
-28
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { useContext, useState } from "react";
2+
import { DeploymentInfoContext } from "dashboard-common/lib/deploymentContext";
3+
import {
4+
ChevronDownIcon,
5+
ChevronRightIcon,
6+
Cross2Icon,
7+
ExternalLinkIcon,
8+
} from "@radix-ui/react-icons";
9+
import { Button } from "dashboard-common/elements/Button";
10+
import { CopyTextButton } from "dashboard-common/elements/CopyTextButton";
11+
import Link from "next/link";
12+
import { cn } from "dashboard-common/lib/cn";
13+
14+
// Little toast to prompt users who are trying out Convex before creating
15+
// an account about the Convex cloud product.
16+
export function ConvexCloudReminderToast() {
17+
const { useCurrentDeployment } = useContext(DeploymentInfoContext);
18+
const deployment = useCurrentDeployment();
19+
const isTryItOutDeployment = deployment?.name?.startsWith("tryitout-");
20+
const [isExpanded, setIsExpanded] = useState(false);
21+
const [isDismissed, setIsDismissed] = useState(false);
22+
23+
if (!isTryItOutDeployment || isDismissed) {
24+
return null;
25+
}
26+
27+
return (
28+
// Positioned in the bottom left corner, high enough to not block the
29+
// sidebar collapse button.
30+
<div className="absolute bottom-12 left-4 z-50">
31+
<div
32+
className="w-96 rounded-lg border border-purple-700 bg-background-secondary shadow-lg"
33+
role="region"
34+
aria-label="Convex cloud notice"
35+
>
36+
<div className="relative">
37+
<Button
38+
variant="unstyled"
39+
className={cn(
40+
"flex w-full cursor-pointer items-center justify-between rounded-lg p-2 text-sm font-medium text-purple-700 hover:bg-background-tertiary focus:outline-none focus:ring-2 focus:ring-purple-700",
41+
isExpanded && "border-b border-purple-500",
42+
)}
43+
onClick={() => setIsExpanded(!isExpanded)}
44+
onKeyDown={(e) => {
45+
if (e.key === "Enter" || e.key === " ") {
46+
setIsExpanded(!isExpanded);
47+
}
48+
}}
49+
aria-expanded={isExpanded}
50+
aria-controls="tryitout-details"
51+
>
52+
<div className="flex items-center gap-2">
53+
{isExpanded ? (
54+
<ChevronDownIcon className="h-4 w-4" />
55+
) : (
56+
<ChevronRightIcon className="h-4 w-4" />
57+
)}
58+
<span>Enjoying Convex? Ready to deploy your app?</span>
59+
</div>
60+
<Button
61+
variant="unstyled"
62+
className="rounded-full p-1 text-purple-700 hover:bg-purple-100"
63+
onClick={(e: React.MouseEvent) => {
64+
e.stopPropagation();
65+
setIsDismissed(true);
66+
}}
67+
aria-label="Dismiss"
68+
>
69+
<Cross2Icon className="h-4 w-4" />
70+
</Button>
71+
</Button>
72+
</div>
73+
{isExpanded && (
74+
<div
75+
id="tryitout-details"
76+
className="flex flex-col gap-2 border-purple-500 px-4 py-3 text-sm text-content-primary"
77+
>
78+
<p>You are currently trying out Convex by running it locally.</p>
79+
<p>
80+
If you're ready to deploy your app and share it with the world or
81+
want to access more features with the cloud product, create a
82+
Convex account and automatically link this deployment:
83+
</p>
84+
<p className="inline-flex items-center gap-2">
85+
Run this in your terminal:
86+
<CopyTextButton text="npx convex login" />
87+
</p>
88+
<Link
89+
href="https://docs.convex.dev"
90+
className="inline-flex items-center gap-2 text-content-link hover:underline"
91+
target="_blank"
92+
>
93+
Learn more about Convex
94+
<ExternalLinkIcon className="h-4 w-4" />
95+
</Link>
96+
</div>
97+
)}
98+
</div>
99+
</div>
100+
);
101+
}

npm-packages/dashboard-self-hosted/src/components/DeploymentCredentialsForm.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export function DeploymentCredentialsForm({
88
initialAdminKey,
99
initialDeploymentUrl,
1010
}: {
11-
onSubmit: (adminKey: string, deploymentUrl: string) => Promise<void>;
11+
onSubmit: (
12+
adminKey: string,
13+
deploymentUrl: string,
14+
deploymentName: string,
15+
) => Promise<void>;
1216
initialAdminKey: string | null;
1317
initialDeploymentUrl: string | null;
1418
}) {
@@ -24,7 +28,7 @@ export function DeploymentCredentialsForm({
2428
className="flex w-[30rem] flex-col gap-2"
2529
onSubmit={(e) => {
2630
e.preventDefault();
27-
void onSubmit(draftAdminKey, draftDeploymentUrl);
31+
void onSubmit(draftAdminKey, draftDeploymentUrl, "");
2832
}}
2933
>
3034
<TextInput

npm-packages/dashboard-self-hosted/src/components/DeploymentList.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ export function DeploymentList({
1515
}: {
1616
listDeploymentsApiUrl: string;
1717
onError: (error: string) => void;
18-
onSelect: (adminKey: string, deploymentUrl: string) => Promise<void>;
18+
onSelect: (
19+
adminKey: string,
20+
deploymentUrl: string,
21+
deploymentName: string,
22+
) => Promise<void>;
1923
}) {
2024
const [lastStoredDeployment, setLastStoredDeployment] = useLocalStorage(
2125
"lastDeployment",
@@ -48,7 +52,11 @@ export function DeploymentList({
4852
(d: Deployment) => d.name === lastStoredDeployment,
4953
);
5054
if (lastDeployment) {
51-
void onSelect(lastDeployment.adminKey, lastDeployment.url);
55+
void onSelect(
56+
lastDeployment.adminKey,
57+
lastDeployment.url,
58+
lastDeployment.name,
59+
);
5260
}
5361
};
5462
void f();
@@ -62,7 +70,7 @@ export function DeploymentList({
6270
variant="neutral"
6371
onClick={() => {
6472
setLastStoredDeployment(d.name);
65-
void onSelect(d.adminKey, d.url);
73+
void onSelect(d.adminKey, d.url, d.name);
6674
}}
6775
>
6876
{d.name}

npm-packages/dashboard-self-hosted/src/pages/_app.tsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Tooltip } from "dashboard-common/elements/Tooltip";
2626
import { DeploymentCredentialsForm } from "components/DeploymentCredentialsForm";
2727
import { DeploymentList } from "components/DeploymentList";
2828
import { checkDeploymentInfo } from "lib/checkDeploymentInfo";
29+
import { ConvexCloudReminderToast } from "components/ConvexCloudReminderToast";
2930

3031
function App({
3132
Component,
@@ -56,7 +57,10 @@ function App({
5657
<DeploymentApiProvider deploymentOverride="local">
5758
<WaitForDeploymentApi>
5859
<DeploymentDashboardLayout>
59-
<Component {...pageProps} />
60+
<>
61+
<Component {...pageProps} />
62+
<ConvexCloudReminderToast />
63+
</>
6064
</DeploymentDashboardLayout>
6165
</WaitForDeploymentApi>
6266
</DeploymentApiProvider>
@@ -158,14 +162,17 @@ const deploymentInfo: Omit<DeploymentInfo, "deploymentUrl" | "adminKey"> = {
158162
slug: "project",
159163
teamId: 0,
160164
}),
161-
useCurrentDeployment: () => ({
162-
id: 0,
163-
name: "local",
164-
deploymentType: "prod",
165-
projectId: 0,
166-
kind: "local",
167-
previewIdentifier: null,
168-
}),
165+
useCurrentDeployment: () => {
166+
const [storedDeploymentName] = useSessionStorage("deploymentName", "");
167+
return {
168+
id: 0,
169+
name: storedDeploymentName,
170+
deploymentType: "dev",
171+
projectId: 0,
172+
kind: "local",
173+
previewIdentifier: null,
174+
};
175+
},
169176
useHasProjectAdminPermissions: () => true,
170177
useIsDeploymentPaused: () => {
171178
const deploymentState = useQuery(udfs.deploymentState.deploymentState);
@@ -217,8 +224,16 @@ function DeploymentInfoProvider({
217224
"deploymentUrl",
218225
"",
219226
);
227+
const [_storedDeploymentName, setStoredDeploymentName] = useSessionStorage(
228+
"deploymentName",
229+
"",
230+
);
220231
const onSubmit = useCallback(
221-
async (submittedAdminKey: string, submittedDeploymentUrl: string) => {
232+
async (
233+
submittedAdminKey: string,
234+
submittedDeploymentUrl: string,
235+
submittedDeploymentName: string,
236+
) => {
222237
const isValid = await checkDeploymentInfo(
223238
submittedAdminKey,
224239
submittedDeploymentUrl,
@@ -233,8 +248,9 @@ function DeploymentInfoProvider({
233248
setIsValidDeploymentInfo(true);
234249
setStoredAdminKey(submittedAdminKey);
235250
setStoredDeploymentUrl(submittedDeploymentUrl);
251+
setStoredDeploymentName(submittedDeploymentName);
236252
},
237-
[setStoredAdminKey, setStoredDeploymentUrl],
253+
[setStoredAdminKey, setStoredDeploymentUrl, setStoredDeploymentName],
238254
);
239255

240256
const finalValue: DeploymentInfo = useMemo(

npm-packages/dashboard-self-hosted/src/pages/settings/index.tsx

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@ import {
55
} from "dashboard-common/features/settings/components/DeploymentUrl";
66
import { DeploymentSettingsLayout } from "dashboard-common/layouts/DeploymentSettingsLayout";
77
import Link from "next/link";
8+
import { useContext } from "react";
9+
import { DeploymentInfoContext } from "dashboard-common/lib/deploymentContext";
10+
import { CopyTextButton } from "dashboard-common/elements/CopyTextButton";
811

912
export default function Settings() {
13+
const { useCurrentDeployment } = useContext(DeploymentInfoContext);
14+
const deployment = useCurrentDeployment();
15+
const isTryItOutDeployment = deployment?.name?.startsWith("tryitout-");
16+
1017
return (
1118
<DeploymentSettingsLayout page="url-and-deploy-key">
1219
<Sheet>
@@ -18,22 +25,40 @@ export default function Settings() {
1825
<HttpActionsUrl />
1926
</Sheet>
2027
<Sheet>
21-
<div className="text-content-primary">
28+
<div className="flex flex-col gap-2 text-content-primary">
2229
<h4 className="mb-4">Deploy Key</h4>
2330

2431
<p className="max-w-prose text-content-secondary">
25-
Deploy keys are not available for self-hosted deployments.{" "}
26-
</p>
27-
<p className="mt-1 max-w-prose text-content-secondary">
28-
Instead, generate an admin key instead using{" "}
29-
<Link
30-
href="https://github.com/get-convex/convex-backend/tree/main/self-hosted#docker-configuration"
31-
className="text-content-link hover:underline"
32-
>
33-
the script in your repository
34-
</Link>
35-
.
32+
Deploy keys are only available for cloud deployments.
3633
</p>
34+
{isTryItOutDeployment ? (
35+
<>
36+
<p className="max-w-prose text-content-primary">
37+
You can create a Convex account and automatically link this
38+
deployment by running this from your terminal:
39+
</p>
40+
41+
<CopyTextButton className="text-sm" text="npx convex login" />
42+
<Link
43+
href="https://docs.convex.dev/production/hosting/"
44+
target="_blank"
45+
className="text-content-link hover:underline"
46+
>
47+
Learn more
48+
</Link>
49+
</>
50+
) : (
51+
<p className="mt-1 max-w-prose text-content-primary">
52+
Instead, generate an admin key instead using{" "}
53+
<Link
54+
href="https://github.com/get-convex/convex-backend/tree/main/self-hosted#docker-configuration"
55+
className="text-content-link hover:underline"
56+
>
57+
the script in your repository
58+
</Link>
59+
.
60+
</p>
61+
)}
3762
</div>
3863
</Sheet>
3964
</DeploymentSettingsLayout>

0 commit comments

Comments
 (0)