diff --git a/src/interface/web/app/settings/page.tsx b/src/interface/web/app/settings/page.tsx index 8f1825c8..e7e4c943 100644 --- a/src/interface/web/app/settings/page.tsx +++ b/src/interface/web/app/settings/page.tsx @@ -1,11 +1,11 @@ -"use client"; +'use client' import styles from "./settings.module.css"; import { Suspense, useEffect, useState } from "react"; import { useToast } from "@/components/ui/use-toast" -import { useUserConfig, ModelOptions } from "../common/auth"; +import { useUserConfig, ModelOptions, UserConfig } from "../common/auth"; import { Button } from "@/components/ui/button"; import { Card, @@ -29,11 +29,25 @@ import { TableRow, } from "@/components/ui/table" -import { ArrowRight, ChatCircleText, Key, Palette, SpeakerHigh, UserCircle, FileMagnifyingGlass, Trash, Copy, PlusCircle } from "@phosphor-icons/react"; +import { + ArrowRight, + ChatCircleText, + Key, + Palette, + SpeakerHigh, + UserCircle, + FileMagnifyingGlass, + Trash, + Copy, + PlusCircle, + CreditCard, + CheckCircle, +} from "@phosphor-icons/react"; import NavMenu from "../components/navMenu/navMenu"; import SidePanel from "../components/sidePanel/chatHistorySidePanel"; import Loading from "../components/loading/loading"; +import { ExternalLinkIcon } from "lucide-react"; interface DropdownComponentProps { @@ -146,9 +160,12 @@ export default function SettingsView() { const [title, setTitle] = useState("Settings"); const [isMobileWidth, setIsMobileWidth] = useState(false); const { apiKeys, generateAPIKey, copyAPIKey, deleteAPIKey } = useApiKeys(); - const userConfig = useUserConfig(true); - const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg"; + const initialUserConfig = useUserConfig(true); + const [userConfig, setUserConfig] = useState(null); const { toast } = useToast(); + const cardClassName = "w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg"; + + useEffect(() => setUserConfig(initialUserConfig), [initialUserConfig]); useEffect(() => { setIsMobileWidth(window.innerWidth < 786); @@ -157,6 +174,38 @@ export default function SettingsView() { return () => window.removeEventListener('resize', handleResize); }, []); + const setSubscription = async (state: string) => { + try { + const url = `/api/subscription?email=${userConfig?.username}&operation=${state}`; + const response = await fetch(url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (!response.ok) throw new Error('Failed to change subscription'); + + // Set updated user settings + if (userConfig) { + let newUserConfig = userConfig; + newUserConfig.subscription_state = state === "cancel" ? "unsubscribed" : "subscribed"; + setUserConfig(newUserConfig); + } + + // Notify user of subscription change + toast({ + title: "💳 Billing", + description: userConfig?.subscription_state === "unsubscribed" ? "Your subscription was cancelled" : "Your Futurist subscription has been renewed", + }); + } catch (error) { + console.error('Error changing subscription:', error); + toast({ + title: "💳 Billing", + description: state === "cancel" ? "Failed to cancel subscription. Try again or contact us at team@khoj.dev" : "Failed to renew subscription. Try again or contact us at team@khoj.dev", + }); + } + }; + const updateModel = (name: string) => async (id: string) => { try { const response = await fetch(`/api/model/${name}?id=` + id, { @@ -166,21 +215,15 @@ export default function SettingsView() { } }); - if (response.ok) { - const result = await response.json(); - toast({ - description: `${name} model updated succesfully`, - }); - } else { - toast({ - description: `Failed to update ${name} model`, - variant: "destructive", - }); - } - } catch (error) { - console.error('Error updating search model:', error); + if (!response.ok) throw new Error('Failed to update model'); + toast({ - description: `An error occured while updating the ${name} model`, + description: `${name} model updated succesfully`, + }); + } catch (error) { + console.error(`Failed to update ${name} model:`, error); + toast({ + description: `Failed to update ${name} model. Try again.`, variant: "destructive", }); } @@ -343,6 +386,75 @@ export default function SettingsView() { +
+
Billing
+
+ + + + Subscription + {(userConfig.subscription_state === "subscribed" || userConfig.subscription_state === "unsubscribed") && ( + + )} + + + {userConfig.subscription_state === "trial" && ( +
+

You are on a 14 day trial of the Khoj Futurist plan

+

See pricing for details

+
+ ) || userConfig.subscription_state === "subscribed" && ( +
+

You are subscribed to Khoj Futurist

+

Subscription will renew on { userConfig.subscription_renewal_date }

+
+ ) || userConfig.subscription_state === "unsubscribed" && ( +
+

You are subscribed to Khoj Futurist

+

Subscription will expire on { userConfig.subscription_renewal_date }

+
+ ) || userConfig.subscription_state === "expired" && ( +
+

Subscribe to the Khoj Futurist plan

+ {userConfig.subscription_renewal_date && ( +

Your subscription expired on { userConfig.subscription_renewal_date }

+ ) || ( +

See pricing for details

+ )} +
+ )} +
+ + {(userConfig.subscription_state == "subscribed") && ( + + ) || (userConfig.subscription_state == "unsubscribed") && ( + + ) || ( + + )} + +
+
+