1+ // Copyright 2025, Command Line Inc.
2+ // SPDX-License-Identifier: Apache-2.0
3+
4+ import Logo from "@/app/asset/logo.svg" ;
5+ import { Button } from "@/app/element/button" ;
6+ import { FlexiModal } from "@/app/modals/modal" ;
7+ import { CurrentOnboardingVersion } from "@/app/onboarding/onboarding-common" ;
8+ import { OnboardingFeatures } from "@/app/onboarding/onboarding-features" ;
9+ import { atoms , globalStore } from "@/app/store/global" ;
10+ import { disableGlobalKeybindings , enableGlobalKeybindings , globalRefocus } from "@/app/store/keymodel" ;
11+ import { modalsModel } from "@/app/store/modalmodel" ;
12+ import * as WOS from "@/app/store/wos" ;
13+ import { RpcApi } from "@/app/store/wshclientapi" ;
14+ import { TabRpcClient } from "@/app/store/wshrpcutil" ;
15+ import { OverlayScrollbarsComponent } from "overlayscrollbars-react" ;
16+ import { useEffect , useRef , useState } from "react" ;
17+ import { debounce } from "throttle-debounce" ;
18+
19+ const UpgradeOnboardingModal_v0_12_0 = ( ) => {
20+ const modalRef = useRef < HTMLDivElement | null > ( null ) ;
21+ const [ pageName , setPageName ] = useState < "welcome" | "features" > ( "welcome" ) ;
22+ const [ isCompact , setIsCompact ] = useState < boolean > ( window . innerHeight < 800 ) ;
23+
24+ const updateModalHeight = ( ) => {
25+ const windowHeight = window . innerHeight ;
26+ setIsCompact ( windowHeight < 800 ) ;
27+ if ( modalRef . current ) {
28+ const modalHeight = modalRef . current . offsetHeight ;
29+ const maxHeight = windowHeight * 0.9 ;
30+ if ( maxHeight < modalHeight ) {
31+ modalRef . current . style . height = `${ maxHeight } px` ;
32+ } else {
33+ modalRef . current . style . height = "auto" ;
34+ }
35+ }
36+ } ;
37+
38+ useEffect ( ( ) => {
39+ updateModalHeight ( ) ;
40+ const debouncedUpdateModalHeight = debounce ( 150 , updateModalHeight ) ;
41+ window . addEventListener ( "resize" , debouncedUpdateModalHeight ) ;
42+ return ( ) => {
43+ window . removeEventListener ( "resize" , debouncedUpdateModalHeight ) ;
44+ } ;
45+ } , [ ] ) ;
46+
47+ useEffect ( ( ) => {
48+ disableGlobalKeybindings ( ) ;
49+ return ( ) => {
50+ enableGlobalKeybindings ( ) ;
51+ } ;
52+ } , [ ] ) ;
53+
54+ const handleStarClick = async ( ) => {
55+ RpcApi . RecordTEventCommand (
56+ TabRpcClient ,
57+ {
58+ event : "onboarding:githubstar" ,
59+ props : { "onboarding:githubstar" : "star" } ,
60+ } ,
61+ { noresponse : true }
62+ ) ;
63+ const clientId = globalStore . get ( atoms . clientId ) ;
64+ await RpcApi . SetMetaCommand ( TabRpcClient , {
65+ oref : WOS . makeORef ( "client" , clientId ) ,
66+ meta : { "onboarding:githubstar" : true } ,
67+ } ) ;
68+ window . open ( "https://github.com/wavetermdev/waveterm?ref=upgrade" , "_blank" ) ;
69+ setPageName ( "features" ) ;
70+ } ;
71+
72+ const handleAlreadyStarred = async ( ) => {
73+ RpcApi . RecordTEventCommand (
74+ TabRpcClient ,
75+ {
76+ event : "onboarding:githubstar" ,
77+ props : { "onboarding:githubstar" : "already" } ,
78+ } ,
79+ { noresponse : true }
80+ ) ;
81+ const clientId = globalStore . get ( atoms . clientId ) ;
82+ await RpcApi . SetMetaCommand ( TabRpcClient , {
83+ oref : WOS . makeORef ( "client" , clientId ) ,
84+ meta : { "onboarding:githubstar" : true } ,
85+ } ) ;
86+ setPageName ( "features" ) ;
87+ } ;
88+
89+ const handleMaybeLater = async ( ) => {
90+ RpcApi . RecordTEventCommand (
91+ TabRpcClient ,
92+ {
93+ event : "onboarding:githubstar" ,
94+ props : { "onboarding:githubstar" : "later" } ,
95+ } ,
96+ { noresponse : true }
97+ ) ;
98+ const clientId = globalStore . get ( atoms . clientId ) ;
99+ await RpcApi . SetMetaCommand ( TabRpcClient , {
100+ oref : WOS . makeORef ( "client" , clientId ) ,
101+ meta : { "onboarding:githubstar" : false } ,
102+ } ) ;
103+ setPageName ( "features" ) ;
104+ } ;
105+
106+ const handleFeaturesComplete = ( ) => {
107+ const clientId = globalStore . get ( atoms . clientId ) ;
108+ RpcApi . SetMetaCommand ( TabRpcClient , {
109+ oref : WOS . makeORef ( "client" , clientId ) ,
110+ meta : { "onboarding:lastversion" : CurrentOnboardingVersion } ,
111+ } ) ;
112+ globalStore . set ( modalsModel . upgradeOnboardingOpen , false ) ;
113+ setTimeout ( ( ) => {
114+ globalRefocus ( ) ;
115+ } , 10 ) ;
116+ } ;
117+
118+ let pageComp : React . JSX . Element = null ;
119+ if ( pageName === "welcome" ) {
120+ pageComp = (
121+ < div className = "flex flex-col h-full" >
122+ < header className = "flex flex-col gap-2 border-b-0 p-0 mt-1 mb-6 w-full unselectable flex-shrink-0" >
123+ < div className = "flex justify-center" >
124+ < Logo />
125+ </ div >
126+ < div className = "text-center text-[25px] font-normal text-foreground" > Welcome to Wave v0.12!</ div >
127+ </ header >
128+ < OverlayScrollbarsComponent
129+ className = "flex-1 overflow-y-auto min-h-0"
130+ options = { { scrollbars : { autoHide : "never" } } }
131+ >
132+ < div className = "flex flex-col items-center gap-3 w-full mb-2 unselectable" >
133+ < div className = "flex flex-col items-center gap-4 max-w-md text-center" >
134+ < div className = "flex h-[52px] px-3 items-center rounded-lg bg-hover text-accent text-[24px]" >
135+ < i className = "fa fa-sparkles" />
136+ < span className = "font-bold ml-2 font-mono" > Wave AI</ span >
137+ </ div >
138+ < div className = "text-secondary leading-relaxed max-w-[420px]" >
139+ < p className = "mb-4" >
140+ Wave AI is your new terminal assistant with full context. It can read your terminal
141+ output, analyze widgets, access files, and help you solve problems faster.
142+ </ p >
143+ < p className = "p-3 border border-border rounded-md bg-hover/30" >
144+ Wave AI is in active beta with included AI credits while we refine the experience.
145+ We're actively improving it and would love your feedback in{ " " }
146+ < a target = "_blank" href = "https://discord.gg/XfvZ334gwU" className = "hover:underline" >
147+ Discord
148+ </ a >
149+ .
150+ </ p >
151+ </ div >
152+ </ div >
153+
154+ < div className = "w-full max-w-md border-t border-border my-2" > </ div >
155+
156+ < div className = "flex flex-col items-center gap-3 text-center max-w-[440px]" >
157+ < div className = "text-foreground text-base" > Thanks for being an early Wave adopter! ⭐</ div >
158+ < div className = "text-secondary text-sm" >
159+ A GitHub star shows your support for Wave (and open-source) and helps us reach more
160+ developers.
161+ </ div >
162+ </ div >
163+ </ div >
164+ </ OverlayScrollbarsComponent >
165+ < footer className = "unselectable flex-shrink-0 mt-4" >
166+ < div className = "flex flex-row items-center justify-center gap-2.5 [&>button]:!px-5 [&>button]:!py-2 [&>button]:text-sm [&>button]:!h-[37px]" >
167+ < Button className = "outlined grey font-[600]" onClick = { handleAlreadyStarred } >
168+ 🙏 Already Starred
169+ </ Button >
170+ < Button className = "outlined green font-[600]" onClick = { handleStarClick } >
171+ ⭐ Star Now
172+ </ Button >
173+ < Button className = "outlined grey font-[600]" onClick = { handleMaybeLater } >
174+ Maybe Later
175+ </ Button >
176+ </ div >
177+ </ footer >
178+ </ div >
179+ ) ;
180+ } else if ( pageName === "features" ) {
181+ pageComp = < OnboardingFeatures onComplete = { handleFeaturesComplete } /> ;
182+ }
183+
184+ if ( pageComp == null ) {
185+ return null ;
186+ }
187+
188+ const paddingClass = isCompact ? "!py-3 !px-[30px]" : "!p-[30px]" ;
189+ const widthClass = pageName === "features" ? "w-[800px]" : "w-[560px]" ;
190+
191+ return (
192+ < FlexiModal className = { `${ widthClass } rounded-[10px] ${ paddingClass } relative overflow-hidden` } ref = { modalRef } >
193+ < div className = "absolute inset-0 bg-gradient-to-br from-accent/[0.25] via-transparent to-accent/[0.05] pointer-events-none rounded-[10px]" />
194+ < div className = "flex flex-col w-full h-full relative z-10" > { pageComp } </ div >
195+ </ FlexiModal >
196+ ) ;
197+ } ;
198+
199+ UpgradeOnboardingModal_v0_12_0 . displayName = "UpgradeOnboardingModal_v0_12_0" ;
200+
201+ export { UpgradeOnboardingModal_v0_12_0 } ;
0 commit comments