2024-07-25 05:06:30 +00:00
'use client'
2024-07-16 14:36:50 +00:00
import styles from "./settings.module.css" ;
import { Suspense , useEffect , useState } from "react" ;
2024-07-18 00:07:19 +00:00
import { useToast } from "@/components/ui/use-toast"
2024-07-16 14:36:50 +00:00
2024-07-25 05:06:30 +00:00
import { useUserConfig , ModelOptions , UserConfig } from "../common/auth" ;
2024-07-25 10:25:33 +00:00
import { isValidPhoneNumber } from 'libphonenumber-js' ;
2024-07-16 14:36:50 +00:00
import { Button } from "@/components/ui/button" ;
import {
Card ,
CardContent ,
CardFooter ,
CardHeader ,
} from "@/components/ui/card" ;
2024-07-17 12:32:51 +00:00
import {
DropdownMenu ,
DropdownMenuContent ,
DropdownMenuRadioGroup ,
DropdownMenuRadioItem ,
DropdownMenuTrigger ,
} from "@/components/ui/dropdown-menu"
2024-07-24 12:47:21 +00:00
import {
Table ,
TableBody ,
TableCell ,
TableHead ,
TableHeader ,
TableRow ,
} from "@/components/ui/table"
2024-07-17 12:32:51 +00:00
2024-07-25 05:06:30 +00:00
import {
ArrowRight ,
ChatCircleText ,
Key ,
Palette ,
SpeakerHigh ,
UserCircle ,
FileMagnifyingGlass ,
Trash ,
Copy ,
PlusCircle ,
CreditCard ,
CheckCircle ,
2024-07-25 05:07:48 +00:00
NotionLogo ,
GithubLogo ,
2024-07-25 10:25:33 +00:00
Files ,
WhatsappLogo ,
ExclamationMark
2024-07-25 05:06:30 +00:00
} from "@phosphor-icons/react" ;
2024-07-17 12:32:51 +00:00
2024-07-16 14:36:50 +00:00
import NavMenu from "../components/navMenu/navMenu" ;
import SidePanel from "../components/sidePanel/chatHistorySidePanel" ;
import Loading from "../components/loading/loading" ;
2024-07-25 05:06:30 +00:00
import { ExternalLinkIcon } from "lucide-react" ;
2024-07-25 10:25:33 +00:00
import { InputOTP , InputOTPGroup , InputOTPSlot } from "@/components/ui/input-otp" ;
import { Input } from "@/components/ui/input" ;
2024-07-16 14:36:50 +00:00
2024-07-17 12:32:51 +00:00
interface DropdownComponentProps {
items : ModelOptions [ ] ;
selected : number ;
2024-07-18 00:07:19 +00:00
callbackFunc : ( value : string ) = > Promise < void > ;
2024-07-16 14:36:50 +00:00
}
2024-07-18 00:07:19 +00:00
const DropdownComponent : React.FC < DropdownComponentProps > = ( { items , selected , callbackFunc } ) = > {
2024-07-17 12:32:51 +00:00
const [ position , setPosition ] = useState ( selected ? . toString ( ) ? ? "0" ) ;
return ! ! selected && (
< div className = "overflow-hidden" >
< DropdownMenu >
< DropdownMenuTrigger asChild className = "w-full" >
< Button variant = "outline" className = "justify-start" >
{ items . find ( item = > item . id === Number ( position ) ) ? . name }
< / Button >
< / DropdownMenuTrigger >
< DropdownMenuContent >
2024-07-18 00:07:19 +00:00
< DropdownMenuRadioGroup
value = { position . toString ( ) }
onValueChange = { async ( value ) = > { setPosition ( value ) ; await callbackFunc ( value ) ; } }
>
2024-07-17 12:32:51 +00:00
{ items . map ( ( item ) = > (
2024-07-25 05:07:48 +00:00
< DropdownMenuRadioItem key = { item . id . toString ( ) } value = { item . id . toString ( ) } >
2024-07-17 12:32:51 +00:00
{ item . name }
< / DropdownMenuRadioItem >
) ) }
< / DropdownMenuRadioGroup >
< / DropdownMenuContent >
< / DropdownMenu >
< / div >
2024-07-16 14:36:50 +00:00
) ;
2024-07-17 12:32:51 +00:00
}
2024-07-16 14:36:50 +00:00
2024-07-24 12:47:21 +00:00
interface TokenObject {
token : string ;
name : string ;
}
export const useApiKeys = ( ) = > {
const [ apiKeys , setApiKeys ] = useState < TokenObject [ ] > ( [ ] ) ;
const { toast } = useToast ( ) ;
const generateAPIKey = async ( ) = > {
try {
const response = await fetch ( ` /auth/token ` , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
} ) ;
const tokenObj = await response . json ( ) ;
setApiKeys ( prevKeys = > [ . . . prevKeys , tokenObj ] ) ;
} catch ( error ) {
console . error ( 'Error generating API key:' , error ) ;
}
} ;
const copyAPIKey = async ( token : string ) = > {
try {
await navigator . clipboard . writeText ( token ) ;
toast ( {
title : "🔑 API Key" ,
description : "Copied to clipboard" ,
} ) ;
} catch ( error ) {
console . error ( 'Error copying API key:' , error ) ;
}
} ;
const deleteAPIKey = async ( token : string ) = > {
try {
const response = await fetch ( ` /auth/token?token= ${ token } ` , { method : 'DELETE' } ) ;
if ( response . ok ) {
setApiKeys ( prevKeys = > prevKeys . filter ( key = > key . token !== token ) ) ;
}
} catch ( error ) {
console . error ( 'Error deleting API key:' , error ) ;
}
} ;
const listApiKeys = async ( ) = > {
try {
const response = await fetch ( ` /auth/token ` ) ;
const tokens = await response . json ( ) ;
if ( tokens ? . length > 0 ) {
setApiKeys ( tokens ) ;
}
} catch ( error ) {
console . error ( 'Error listing API keys:' , error ) ;
}
} ;
useEffect ( ( ) = > {
listApiKeys ( ) ;
} , [ ] ) ;
return {
apiKeys ,
generateAPIKey ,
copyAPIKey ,
deleteAPIKey ,
} ;
} ;
2024-07-25 10:25:33 +00:00
enum PhoneNumberValidationState {
Setup = "setup" ,
SendOTP = "otp" ,
VerifyOTP = "verify" ,
Verified = "verified" ,
}
2024-07-24 12:47:21 +00:00
2024-07-16 14:36:50 +00:00
export default function SettingsView() {
const [ title , setTitle ] = useState ( "Settings" ) ;
const [ isMobileWidth , setIsMobileWidth ] = useState ( false ) ;
2024-07-24 12:47:21 +00:00
const { apiKeys , generateAPIKey , copyAPIKey , deleteAPIKey } = useApiKeys ( ) ;
2024-07-25 05:06:30 +00:00
const initialUserConfig = useUserConfig ( true ) ;
const [ userConfig , setUserConfig ] = useState < UserConfig | null > ( null ) ;
2024-07-25 10:25:33 +00:00
const [ number , setNumber ] = useState < string | undefined > ( undefined ) ;
const [ otp , setOTP ] = useState ( "" ) ;
const [ numberValidationState , setNumberValidationState ] = useState < PhoneNumberValidationState > ( PhoneNumberValidationState . Verified ) ;
2024-07-18 00:07:19 +00:00
const { toast } = useToast ( ) ;
2024-07-25 15:17:34 +00:00
const cardClassName = "w-full lg:w-1/3 grid grid-flow-column border border-gray-300 shadow-md rounded-lg" ;
2024-07-25 05:06:30 +00:00
2024-07-25 10:25:33 +00:00
useEffect ( ( ) = > {
setUserConfig ( initialUserConfig ) ;
setNumber ( initialUserConfig ? . phone_number ) ;
setNumberValidationState (
initialUserConfig ? . is_phone_number_verified
? PhoneNumberValidationState . Verified
: initialUserConfig ? . phone_number
? PhoneNumberValidationState . SendOTP
: PhoneNumberValidationState . Setup
) ;
} , [ initialUserConfig ] ) ;
2024-07-16 14:36:50 +00:00
useEffect ( ( ) = > {
setIsMobileWidth ( window . innerWidth < 786 ) ;
const handleResize = ( ) = > setIsMobileWidth ( window . innerWidth < 786 ) ;
window . addEventListener ( 'resize' , handleResize ) ;
return ( ) = > window . removeEventListener ( 'resize' , handleResize ) ;
} , [ ] ) ;
2024-07-25 12:49:58 +00:00
const sendOTP = async ( ) = > {
try {
const response = await fetch ( ` /api/phone?phone_number= ${ number } ` , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
} ) ;
if ( ! response . ok ) throw new Error ( 'Failed to send OTP' ) ;
setNumberValidationState ( PhoneNumberValidationState . VerifyOTP ) ;
} catch ( error ) {
console . error ( 'Error sending OTP:' , error ) ;
toast ( {
title : "📱 Phone" ,
description : "Failed to send OTP. Try again or contact us at team@khoj.dev" ,
} ) ;
}
} ;
const verifyOTP = async ( ) = > {
try {
const response = await fetch ( ` /api/phone/verify?code= ${ otp } ` , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
} ,
} ) ;
if ( ! response . ok ) throw new Error ( 'Failed to verify OTP' ) ;
setNumberValidationState ( PhoneNumberValidationState . Verified ) ;
toast ( {
title : "📱 Phone" ,
description : "Phone number verified" ,
} ) ;
} catch ( error ) {
console . error ( 'Error verifying OTP:' , error ) ;
toast ( {
title : "📱 Phone" ,
description : "Failed to verify OTP. Try again or contact us at team@khoj.dev" ,
} ) ;
}
} ;
2024-07-25 05:06:30 +00:00
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" ,
} ) ;
}
} ;
2024-07-18 00:07:19 +00:00
const updateModel = ( name : string ) = > async ( id : string ) = > {
try {
const response = await fetch ( ` /api/model/ ${ name } ?id= ` + id , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
}
} ) ;
2024-07-25 05:06:30 +00:00
if ( ! response . ok ) throw new Error ( 'Failed to update model' ) ;
toast ( {
description : ` ${ name } model updated succesfully ` ,
} ) ;
2024-07-18 00:07:19 +00:00
} catch ( error ) {
2024-07-25 05:06:30 +00:00
console . error ( ` Failed to update ${ name } model: ` , error ) ;
2024-07-18 00:07:19 +00:00
toast ( {
2024-07-25 05:06:30 +00:00
description : ` Failed to update ${ name } model. Try again. ` ,
2024-07-18 00:07:19 +00:00
variant : "destructive" ,
} ) ;
}
} ;
if ( ! userConfig ) return < Loading / > ;
return (
2024-07-16 14:36:50 +00:00
< div id = "page" className = { styles . page } >
< title >
{ title }
< / title >
< div className = { styles . sidePanel } >
< SidePanel
webSocketConnected = { true }
conversationId = { null }
uploadedFiles = { [ ] }
isMobileWidth = { isMobileWidth }
/ >
< / div >
< div className = { styles . content } >
< NavMenu selected = "Settings" title = "Settings" showLogo = { true } / >
< div className = { styles . contentBody } >
< Suspense fallback = { < Loading / > } >
2024-07-25 15:17:34 +00:00
< div id = "content" className = "grid grid-flow-column sm:grid-flow-row gap-16 m-8" >
2024-07-16 14:36:50 +00:00
< div className = "section grid gap-8" >
< div className = "text-4xl" > Profile < / div >
< div className = "cards flex flex-wrap gap-16" >
< Card className = { cardClassName } >
2024-07-17 17:21:03 +00:00
< CardHeader className = "text-xl flex flex-row" > < UserCircle className = "h-7 w-7 mr-2" / > Name < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
< input type = "text" className = "w-full border border-gray-300 rounded-lg p-4" defaultValue = { userConfig . given_name } / >
2024-07-16 14:36:50 +00:00
< / CardContent >
2024-07-17 12:32:51 +00:00
< CardFooter className = "flex flex-wrap gap-4" >
< Button variant = "outline" size = "sm" className = "border-green-400" > Save < / Button >
2024-07-16 14:36:50 +00:00
< / CardFooter >
< / Card >
< / div >
< / div >
< div className = "section grid gap-8" >
< div className = "text-4xl" > Content < / div >
< div className = "cards flex flex-wrap gap-16" >
< Card className = { cardClassName } >
2024-07-25 05:07:48 +00:00
< CardHeader className = "text-xl flex flex-row" > < Files className = "h-7 w-7 mr-2" / > Files < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
2024-07-16 14:36:50 +00:00
Manage your synced files
< / CardContent >
2024-07-17 12:32:51 +00:00
< CardFooter className = "flex flex-wrap gap-4" >
< Button variant = "outline" size = "sm" className = "border-green-400" > { userConfig . enabled_content_source . computer ? "Update" : "Setup" } < ArrowRight className = "inline ml-2" weight = "bold" / > < / Button >
< Button variant = "outline" size = "sm" className = { ` ${ userConfig . enabled_content_source . computer ? "border-red-400" : "hidden" } ` } > Disable < / Button >
2024-07-16 14:36:50 +00:00
< / CardFooter >
< / Card >
< Card className = { cardClassName } >
2024-07-25 05:07:48 +00:00
< CardHeader className = "text-xl flex flex-row" > < GithubLogo className = "h-7 w-7 mr-2" / > Github < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
2024-07-16 14:36:50 +00:00
Set repositories to index
< / CardContent >
2024-07-17 12:32:51 +00:00
< CardFooter className = "flex flex-wrap gap-4" >
< Button variant = "outline" size = "sm" className = "border-green-400" > { userConfig . enabled_content_source . github ? "Update" : "Setup" } < ArrowRight className = "inline ml-2" weight = "bold" / > < / Button >
< Button variant = "outline" size = "sm" className = { ` ${ userConfig . enabled_content_source . github ? "border-red-400" : "hidden" } ` } > Disable < / Button >
2024-07-16 14:36:50 +00:00
< / CardFooter >
< / Card >
< Card className = { cardClassName } >
2024-07-25 05:07:48 +00:00
< CardHeader className = "text-xl flex flex-row" > < NotionLogo className = "h-7 w-7 mr-2" / > Notion < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
2024-07-16 14:36:50 +00:00
Sync your Notion pages
< / CardContent >
2024-07-17 12:32:51 +00:00
< CardFooter className = "flex flex-wrap gap-4" >
< Button variant = "outline" size = "sm" className = "border-green-400" > { userConfig . enabled_content_source . notion ? "Update" : "Setup" } < ArrowRight className = "inline ml-2" weight = "bold" / > < / Button >
< Button variant = "outline" size = "sm" className = { ` ${ userConfig . enabled_content_source . notion ? "border-red-400" : "hidden" } ` } > Disable < / Button >
2024-07-16 14:36:50 +00:00
< / CardFooter >
< / Card >
2024-07-17 12:32:51 +00:00
< / div >
< / div >
< div className = "section grid gap-8" >
< div className = "text-4xl" > Features < / div >
< div className = "cards flex flex-wrap gap-16" >
< Card className = { cardClassName } >
2024-07-17 17:21:03 +00:00
< CardHeader className = "text-xl flex flex-row" > < ChatCircleText className = "h-7 w-7 mr-2" / > Chat < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
< DropdownComponent
items = { userConfig . chat_model_options }
selected = { userConfig . selected_chat_model_config }
2024-07-18 00:07:19 +00:00
callbackFunc = { updateModel ( "chat" ) }
/ >
< / CardContent >
< / Card >
< Card className = { cardClassName } >
< CardHeader className = "text-xl flex flex-row" > < FileMagnifyingGlass className = "h-7 w-7 mr-2" / > Search < / CardHeader >
< CardContent className = "overflow-hidden" >
< DropdownComponent
items = { userConfig . search_model_options }
selected = { userConfig . selected_search_model_config }
callbackFunc = { updateModel ( "search" ) }
2024-07-17 12:32:51 +00:00
/ >
< / CardContent >
< / Card >
< Card className = { cardClassName } >
2024-07-17 17:21:03 +00:00
< CardHeader className = "text-xl flex flex-row" > < Palette className = "h-7 w-7 mr-2" / > Paint < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
< DropdownComponent
items = { userConfig . paint_model_options }
selected = { userConfig . selected_paint_model_config }
2024-07-18 00:07:19 +00:00
callbackFunc = { updateModel ( "paint" ) }
2024-07-17 12:32:51 +00:00
/ >
< / CardContent >
< / Card >
< Card className = { cardClassName } >
2024-07-17 17:21:03 +00:00
< CardHeader className = "text-xl flex flex-row" > < SpeakerHigh className = "h-7 w-7 mr-2" / > Voice < / CardHeader >
2024-07-17 12:32:51 +00:00
< CardContent className = "overflow-hidden" >
< DropdownComponent
items = { userConfig . voice_model_options }
selected = { userConfig . selected_voice_model_config }
2024-07-18 00:07:19 +00:00
callbackFunc = { updateModel ( "voice" ) }
2024-07-17 12:32:51 +00:00
/ >
2024-07-16 14:36:50 +00:00
< / CardContent >
< / Card >
< / div >
< / div >
2024-07-24 12:47:21 +00:00
< div className = "section grid gap-8" >
< div className = "text-4xl" > Clients < / div >
< div className = "cards flex flex-wrap gap-16" >
< Card className = "grid grid-flow-column border border-gray-300 shadow-md rounded-lg" >
< CardHeader className = "text-xl flex flex-row" > < Key className = "h-7 w-7 mr-2" / > API Keys < / CardHeader >
< CardContent className = "overflow-hidden" >
< p className = "text-md text-gray-400" >
Access Khoj from your Desktop App , Obsidian plugin , and more .
< / p >
< Table >
< TableHeader >
< TableRow >
2024-07-25 15:17:34 +00:00
< TableHead className = "pl-0" > Name < / TableHead >
2024-07-24 12:47:21 +00:00
< TableHead > Token < / TableHead >
< TableHead > Actions < / TableHead >
< / TableRow >
< / TableHeader >
< TableBody >
{ apiKeys . map ( ( key ) = > (
< TableRow key = { key . token } >
2024-07-25 15:17:34 +00:00
< TableCell className = "pl-0" > < b > { key . name } < / b > < / TableCell >
2024-07-24 12:47:21 +00:00
< TableCell > { ` ${ key . token . slice ( 0 , 4 ) } ... ${ key . token . slice ( - 4 ) } ` } < / TableCell >
< TableCell >
< Button variant = "outline" className = "border border-green-400" onClick = { ( ) = > copyAPIKey ( key . token ) } >
< Copy className = "h-4 w-4 mr-2" / > < span className = "hidden md:inline" > Copy < / span >
< / Button >
2024-07-25 15:17:34 +00:00
< Button variant = "outline" className = "md:ml-4 border border-red-400" onClick = { ( ) = > deleteAPIKey ( key . token ) } >
2024-07-24 12:47:21 +00:00
< Trash className = 'h-4 w-4 mr-2' / > < span className = "hidden md:inline" > Delete < / span >
< / Button >
< / TableCell >
< / TableRow >
) ) }
< / TableBody >
< / Table >
< / CardContent >
< CardFooter className = "flex flex-wrap gap-4" >
< Button variant = "outline" className = "border border-green-300" onClick = { generateAPIKey } >
< PlusCircle className = 'h-4 w-4 mr-2' / > Generate Key
< / Button >
< / CardFooter >
< / Card >
2024-07-25 10:25:33 +00:00
< Card className = { cardClassName } >
< CardHeader className = "text-xl flex flex-row" >
< WhatsappLogo className = "h-7 w-7 mr-2" / >
Chat on Whatsapp
{ numberValidationState === PhoneNumberValidationState . Verified && (
< CheckCircle weight = "bold" className = "h-4 w-4 ml-1 text-green-400" / >
) || numberValidationState !== PhoneNumberValidationState . Setup && (
< ExclamationMark weight = "bold" className = "h-4 w-4 ml-1 text-yellow-400" / >
) }
< / CardHeader >
< CardContent className = "grid gap-4" >
< p >
Connect your number to chat with Khoj on WhatsApp . Learn more about the integration < a href = "https://docs.khoj.dev/clients/whatsapp" > here < / a > .
< / p >
< div >
{ numberValidationState === PhoneNumberValidationState . VerifyOTP && (
< div >
< p > { ` Enter the OTP sent to your WhatsApp number: ${ number } ` } < / p >
< InputOTP
autoFocus = { true }
maxLength = { 6 }
value = { otp }
onChange = { setOTP }
onComplete = { ( ) = > setNumberValidationState ( PhoneNumberValidationState . Verified ) }
>
< InputOTPGroup >
< InputOTPSlot index = { 0 } / >
< InputOTPSlot index = { 1 } / >
< InputOTPSlot index = { 2 } / >
< InputOTPSlot index = { 3 } / >
< InputOTPSlot index = { 4 } / >
< InputOTPSlot index = { 5 } / >
< / InputOTPGroup >
< / InputOTP >
< / div >
) || (
< div >
< Input
onChange = { ( e ) = > setNumber ( e . target . value ) }
value = { number }
placeholder = "Enter your WhatsApp number"
className = "w-full border border-gray-300 rounded-lg p-4"
/ >
< / div >
) }
< / div >
< / CardContent >
< CardFooter className = "flex flex-wrap gap-4" >
{ numberValidationState === PhoneNumberValidationState . VerifyOTP && (
< Button
variant = "outline"
className = "border border-green-400"
2024-07-25 12:49:58 +00:00
onClick = { verifyOTP }
2024-07-25 10:25:33 +00:00
>
Verify
< / Button >
) || (
< Button
variant = "outline"
className = "border border-green-400"
disabled = { ! number || number === userConfig . phone_number || ! isValidPhoneNumber ( number ) }
2024-07-25 12:49:58 +00:00
onClick = { sendOTP }
2024-07-25 10:25:33 +00:00
>
{ ! number || number === userConfig . phone_number || ! isValidPhoneNumber ( number )
? "Update"
: (
< > Send OTP to Whatsapp < ArrowRight className = "inline ml-2" weight = "bold" / > < / >
) }
< / Button >
) }
< / CardFooter >
< / Card >
2024-07-24 12:47:21 +00:00
< / div >
< / div >
2024-07-25 05:06:30 +00:00
< div className = "section grid gap-8" >
< div className = "text-4xl" > Billing < / div >
< div className = "cards flex flex-wrap gap-16" >
< Card className = { cardClassName } >
< CardHeader className = "text-xl flex flex-row" >
< CreditCard className = "h-7 w-7 mr-2" / >
Subscription
{ ( userConfig . subscription_state === "subscribed" || userConfig . subscription_state === "unsubscribed" ) && (
< CheckCircle weight = "bold" className = "h-4 w-4 ml-1 text-green-400" / >
) }
< / CardHeader >
< CardContent className = "overflow-hidden" >
{ userConfig . subscription_state === "trial" && (
< div >
< p > You are on a 14 day trial of the Khoj < i > Futurist < / i > plan < / p >
< p > See < a href = "https://khoj.dev/pricing" > pricing < / a > for details < / p >
< / div >
) || userConfig . subscription_state === "subscribed" && (
< div >
< p > You are < b > subscribed < / b > to Khoj < i > Futurist < / i > < / p >
< p > Subscription will < b > renew < / b > on < b > { userConfig . subscription_renewal_date } < / b > < / p >
< / div >
) || userConfig . subscription_state === "unsubscribed" && (
< div >
< p > You are < b > subscribed < / b > to Khoj < i > Futurist < / i > < / p >
< p > Subscription will < b > expire < / b > on < b > { userConfig . subscription_renewal_date } < / b > < / p >
< / div >
) || userConfig . subscription_state === "expired" && (
< div >
< p > Subscribe to the Khoj < i > Futurist < / i > plan < / p >
{ userConfig . subscription_renewal_date && (
< p > Your subscription < b > expired < / b > on < b > { userConfig . subscription_renewal_date } < / b > < / p >
) || (
< p > See < a href = "https://khoj.dev/pricing" > pricing < / a > for details < / p >
) }
< / div >
) }
< / CardContent >
< CardFooter className = "flex flex-wrap gap-4" >
{ ( userConfig . subscription_state == "subscribed" ) && (
< Button
variant = "outline"
className = "border border-red-400"
onClick = { ( ) = > setSubscription ( "cancel" ) }
>
Unsubscribe
< / Button >
) || ( userConfig . subscription_state == "unsubscribed" ) && (
< Button
variant = "outline"
className = "border border-green-400"
onClick = { ( ) = > setSubscription ( "resubscribe" ) }
>
Resubscribe
< / Button >
) || (
< Button
variant = "outline"
className = "border border-green-400"
onClick = { ( ) = > window . open ( ` ${ userConfig . khoj_cloud_subscription_url } ?prefilled_email= ${ userConfig . username } ` , '_blank' , 'noopener,noreferrer' ) }
>
Subscribe
< ExternalLinkIcon className = "h-4 w-4 ml-1" / >
< / Button >
) }
< / CardFooter >
< / Card >
< / div >
< / div >
2024-07-16 14:36:50 +00:00
< / div >
< / Suspense >
< / div >
< / div >
< / div >
) ;
}