Skip to content

Commit

Permalink
Add error modal when you run out of tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
SawyerHood committed Aug 5, 2024
1 parent 2070298 commit f4defff
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 17 deletions.
1 change: 1 addition & 0 deletions components/programs/Alert.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
display: flex;
justify-content: flex-end;
margin-top: 16px;
gap: 8px;
}
19 changes: 14 additions & 5 deletions components/programs/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode } from "react";
import React from "react";
import styles from "./Alert.module.css";
import { useSetAtom, useAtomValue } from "jotai";
import { windowsListAtom } from "@/state/windowsList";
Expand All @@ -19,7 +19,18 @@ export function Alert({ id }: { id: string }) {
return null;
}

const { message, icon } = windowState.program;
const { message, icon, actions } = windowState.program;

const renderActions = () => {
if (actions && actions.length > 0) {
return actions.map((action, index) => (
<button key={index} onClick={() => action.callback(handleClose)}>
{action.label}
</button>
));
}
return <button onClick={handleClose}>OK</button>;
};

return (
<div className={styles.alertContainer}>
Expand All @@ -31,9 +42,7 @@ export function Alert({ id }: { id: string }) {
)}
<div className={styles.alertMessage}>{message}</div>
</div>
<div className={styles.alertActions}>
<button onClick={handleClose}>OK</button>
</div>
<div className={styles.alertActions}>{renderActions()}</div>
</div>
);
}
4 changes: 4 additions & 0 deletions components/programs/Iframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getURLForProgram } from "@/lib/getURLForProgram";
import { getSettings } from "@/lib/getSettings";
import { settingsAtom } from "@/state/settings";
import wrappedFetch from "@/lib/wrappedFetch";
import { showUpsell } from "@/lib/showUpsell";

export function Iframe({ id }: { id: string }) {
const window = useAtomValue(windowAtomFamily(id));
Expand Down Expand Up @@ -163,6 +164,9 @@ function IframeInner({ id }: { id: string }) {
src={!program?.code ? url : undefined}
srcDoc={program?.code || undefined}
style={{ width: "100%", flexGrow: 1, border: "none" }}
onError={() => {
showUpsell();
}}
onLoad={() => {
assert(state.program.type === "iframe", "Program is not an iframe");

Expand Down
18 changes: 8 additions & 10 deletions lib/alert.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { createWindow } from "@/lib/createWindow";
import { getDefaultStore } from "jotai";
import { WindowState } from "@/state/window";
import { AlertAction, WindowState } from "@/state/window";
import { allWindowsAtom } from "@/state/allWindows";
import { ReactNode } from "react";

type AlertOptions = {
message: ReactNode;
alertId?: string;
icon?: "x";
actions?: AlertAction[];
};

export function alert({ message, alertId, icon }: AlertOptions) {
export function alert({ message, alertId, icon, actions }: AlertOptions) {
const store = getDefaultStore();
const existingWindows = store.get(allWindowsAtom);

Expand All @@ -27,19 +28,16 @@ export function alert({ message, alertId, icon }: AlertOptions) {

createWindow({
title: "Alert",
size: {
width: 400,
height: "auto",
},
program: {
type: "alert",
message,
alertId,
icon,
actions,
},
});
}

declare global {
interface Window {
myAlert: typeof alert;
}
}

window.myAlert = alert;
48 changes: 48 additions & 0 deletions lib/showUpsell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { settingsAtom } from "@/state/settings";
import { getDefaultStore } from "jotai";
import { alert } from "./alert";

export function showUpsell() {
alert({
alertId: "OUT_OF_CREDITS",
message: (
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<p>
You&apos;ve used all your available tokens for high-quality
generations with Claude 3.5 Sonnet.
</p>
<p>To continue using Windows 9X, you have two options:</p>
<ul style={{ paddingLeft: "20px", marginTop: "4px" }}>
<li>Purchase additional quality tokens</li>
<li>Switch to our free model (with reduced capabilities)</li>
</ul>
</div>
),
icon: "x",
actions: [
{
label: "Use Free Model",
callback: (close) => {
getDefaultStore().set(settingsAtom, {
...getDefaultStore().get(settingsAtom),
model: "cheap",
});
close();
},
},
{
label: "Get Tokens",
callback: (close) => {
const form = document.createElement("form");
form.method = "POST";
form.action = "/api/checkout";
form.target = "_blank";
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
close();
},
},
],
});
}
4 changes: 3 additions & 1 deletion lib/wrappedFetch.ts → lib/wrappedFetch.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { showUpsell } from "./showUpsell";

export async function wrappedFetch(
input: RequestInfo | URL,
init?: RequestInit
): Promise<Response> {
try {
const response = await fetch(input, init);
if (!response.ok && response.status === 402) {
// TODO: show an alert to the user.
showUpsell();
}
return response;
} catch (error) {
Expand Down
12 changes: 11 additions & 1 deletion state/window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,18 @@ export type Program =
action?: (path: string) => void;
actionText?: string;
}
| { type: "alert"; message: ReactNode; alertId?: string; icon?: "x" };
| {
type: "alert";
message: ReactNode;
alertId?: string;
icon?: "x";
actions?: AlertAction[];
};

export type AlertAction = {
label: string;
callback: (close: () => void) => void;
};
export type WindowState = {
status: "maximized" | "minimized" | "normal";
pos: {
Expand Down

0 comments on commit f4defff

Please sign in to comment.