1- < div x-show ="$store.installer.currentStep === 3 " class ="animate-fade-in " x-data ="domainSetup() " x-init ="init() ">
2- < h2 class ="text-xl font-semibold mb-6 text-gray-900 "> Domain</ h2 >
3-
4- < div class ="mb-5 ">
5- < label class ="block text-sm font-medium mb-2 text-gray-900 "> Domain (Optional)</ label >
6- < input
7- type ="text "
8- x-model ="$store.installer.domain "
9- name ="domain "
10- id ="domain-input "
11- placeholder ="example.com "
12- autocomplete ="off "
13- class ="w-full px-3 py-2.5 bg-white border border-gray-300 rounded-md text-gray-900 outline-none transition-all duration-200 text-sm font-sans focus:border-gray-900 "
14- @input ="updateDomain($event.target.value) "
15- />
16- < p class ="text-xs text-gray-400 mt-1.5 "> Leave empty to skip domain setup</ p >
17-
18- <!-- Domain Status Message -->
19- < div x-show ="domainStatusMessage " class ="mt-2 text-sm " :class ="$store.installer.domainVerified ? 'text-green-600' : 'text-red-600' ">
20- < span x-text ="domainStatusMessage "> </ span >
1+ < div x-show ="$store.installer.currentStep === 3 " class ="animate-fade-in " x-data ="domainSetup() ">
2+ < h2 class ="text-xl font-semibold mb-3 text-gray-900 "> Add Domain</ h2 >
3+
4+ < p class ="text-sm text-gray-600 mb-6 ">
5+ To add a custom domain, you must already own it. If you don't have one, skip this step and we'll provide you
6+ with a free Traefik domain.
7+ </ p >
8+
9+ < div class ="mb-5 ">
10+ < label class ="block text-sm font-medium mb-2 text-gray-900 "> Domain (Optional)</ label >
11+ < input type ="text " x-model ="$store.installer.domain " name ="domain " id ="domain-input " placeholder ="example.com "
12+ autocomplete ="off "
13+ class ="w-full px-3 py-2.5 bg-white border border-gray-300 rounded-md text-gray-900 outline-none transition-all duration-200 text-sm font-sans focus:border-gray-900 "
14+ @input ="updateDomain($event.target.value) " />
15+ < p class ="text-xs text-gray-400 mt-1.5 "> Leave empty to skip domain setup</ p >
16+
17+ < div x-show ="$store.installer.domain && $store.installer.domain.trim() " class ="mt-3 ">
18+ < div class ="bg-blue-50 border border-blue-200 rounded-md p-4 ">
19+ < p class ="text-sm text-gray-700 mb-2 "> Point your domain to the server by creating an < span
20+ class ="font-semibold "> A record</ span > :</ p >
21+ < p class ="font-mono text-sm text-gray-900 font-medium ">
22+ < span x-text ="$store.installer.domain "> </ span > → < span
23+ x-text ="$store.installer.serverDetails?.ip || '0.0.0.0' "> </ span >
24+ </ p >
25+ </ div >
26+ </ div >
27+ </ div >
28+
29+ < div class ="flex gap-3 mt-6 ">
30+ < button @click ="prevStep() "
31+ class ="flex-1 px-4 py-2.5 rounded-md font-medium border border-gray-300 cursor-pointer transition-all duration-200 text-sm bg-white text-gray-900 hover:bg-gray-50 ">
32+ Back
33+ </ button >
34+
35+ < button @click ="nextStep() " :disabled ="isLoading "
36+ class ="flex-1 px-4 py-2.5 rounded-md font-medium border border-gray-900 cursor-pointer transition-all duration-200 text-sm disabled:cursor-not-allowed inline-flex justify-center items-center gap-2 bg-gray-900 text-white hover:bg-gray-800 disabled:opacity-50 ">
37+ < svg x-show ="isLoading " class ="animate-spin h-5 w-5 text-gray-950 " xmlns ="http://www.w3.org/2000/svg "
38+ fill ="none " viewBox ="0 0 24 24 ">
39+ < circle class ="opacity-25 " cx ="12 " cy ="12 " r ="10 " stroke ="currentColor " stroke-width ="4 "> </ circle >
40+ < path class ="opacity-75 " fill ="currentColor "
41+ d ="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z ">
42+ </ path >
43+ </ svg >
44+ < span
45+ x-text ="isLoading ? 'Verifying...' : ($store.installer.domain && $store.installer.domain.trim() && !$store.installer.domainVerified ? 'Verify & Continue' : 'Continue') "> </ span >
46+ </ button >
2147 </ div >
22- </ div >
23-
24- < div class ="flex gap-3 mt-6 ">
25- < button
26- @click ="prevStep() "
27- class ="flex-1 px-4 py-2.5 rounded-md font-medium border border-gray-300 cursor-pointer transition-all duration-200 text-sm bg-white text-gray-900 hover:bg-gray-50 "
28- >
29- Back
30- </ button >
31-
32- <!-- Dynamic Button -->
33- < button
34- @click ="nextStep() "
35- :disabled ="isLoading "
36- class ="flex-1 px-4 py-2.5 rounded-md font-medium border border-gray-900 cursor-pointer transition-all duration-200 text-sm disabled:cursor-not-allowed inline-flex justify-center items-center gap-2 bg-gray-900 text-white hover:bg-gray-800 disabled:opacity-50 "
37- >
38- <!-- Spinner -->
39- < svg x-show ="isLoading " class ="animate-spin h-5 w-5 text-gray-950 " xmlns ="http://www.w3.org/2000/svg " fill ="none "
40- viewBox ="0 0 24 24 ">
41- < circle class ="opacity-25 " cx ="12 " cy ="12 " r ="10 " stroke ="currentColor " stroke-width ="4 "> </ circle >
42- < path class ="opacity-75 " fill ="currentColor "
43- d ="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z ">
44- </ path >
45- </ svg >
46- < span x-text ="isLoading ? 'Verifying...' : ($store.installer.domain && $store.installer.domain.trim() && !$store.installer.domainVerified ? 'Verify & Continue' : 'Continue') "> </ span >
47- </ button >
48- </ div >
4948</ div >
5049
5150< script >
52- function domainSetup ( ) {
53- return {
54- isLoading : false ,
55- domainStatusMessage : '' ,
56-
57- // Initialize from store
58- init ( ) {
59- if ( this . $store . installer . domainVerified && this . $store . installer . domain ) {
60- this . domainStatusMessage = `✅ ${ this . $store . installer . domain } is verified` ;
61- }
62- } ,
63-
64- updateDomain ( value ) {
65- if ( this . $store . installer . domainVerified && value !== this . $store . installer . domain ) {
66- this . $store . installer . domainVerified = false ;
67- this . domainStatusMessage = '' ;
68- }
69- this . $store . installer . domain = value ;
70- } ,
71-
72- prevStep ( ) {
73- this . $store . installer . currentStep = 2 ;
74- } ,
75-
76- async nextStep ( ) {
77- const domain = this . $store . installer . domain ;
78-
79- if ( domain && domain . trim ( ) ) {
80- // Check if domain is already verified
81- if ( this . $store . installer . domainVerified ) {
82- this . domainStatusMessage = `✅ ${ domain } is verified` ;
83- this . $store . installer . currentStep = 4 ;
84- return ;
51+ function domainSetup ( ) {
52+ return {
53+ isLoading : false ,
54+
55+ updateDomain ( value ) {
56+ if ( this . $store . installer . domainVerified && value !== this . $store . installer . domain ) {
57+ this . $store . installer . domainVerified = false ;
8558 }
86-
87- // Domain verification step
88- this . isLoading = true ;
89- this . domainStatusMessage = '' ;
59+ this . $store . installer . domain = value ;
60+ } ,
9061
62+ async verifyDomain ( domain , expectedIp ) {
63+ const url = '/api/method/nano_press.api.check_domain_resolves_to_ip' ;
9164 try {
92- const expectedIp = this . $store . installer . serverDetails ?. ip ;
93-
94- if ( ! expectedIp ) {
95- this . domainStatusMessage = "⚠️ Please enter a server IP address in the Server step to continue" ;
96- if ( typeof toast !== 'undefined' ) {
65+ const payload = {
66+ domain : domain ,
67+ expected_ip : expectedIp
68+ } ;
69+
70+ const response = await frappe_call ( url , payload , "POST" ) ;
71+
72+ if ( ! response ?. message ?. success ) {
73+ throw new Error ( response ?. message ?. message || "Domain verification failed" ) ;
74+ }
75+
76+ const message = response . message . message ;
77+ toast ( message , { type : "success" } ) ;
78+ return response . message ;
79+ } catch ( err ) {
80+ const message = err . message || "Failed to verify domain" ;
81+ toast ( message , { type : "danger" } ) ;
82+ throw err ;
83+ }
84+ } ,
85+
86+ async nextStep ( ) {
87+ const domain = this . $store . installer . domain ;
88+
89+ if ( domain && domain . trim ( ) ) {
90+ // Always verify domain - don't skip even if previously verified
91+ this . isLoading = true ;
92+
93+ try {
94+ const expectedIp = this . $store . installer . serverDetails ?. ip ;
95+
96+ if ( ! expectedIp ) {
9797 toast ( "Please enter a server IP address to continue" , { type : "danger" } ) ;
98+ this . isLoading = false ;
99+ return ;
98100 }
101+
102+ await this . verifyDomain ( domain , expectedIp ) ;
103+
104+ this . $store . installer . domainVerified = true ;
105+
106+ setTimeout ( ( ) => {
107+ this . $store . installer . currentStep ++ ;
108+ } , 1000 ) ;
109+ } catch ( err ) {
110+ console . error ( '❌ Domain verification failed:' , err ) ;
111+ this . $store . installer . domainVerified = false ;
112+ } finally {
99113 this . isLoading = false ;
100- return ;
101114 }
102-
103- await verify_domain ( domain , expectedIp ) ;
104-
105- // Mark as verified in store
106- this . $store . installer . domainVerified = true ;
107- this . domainStatusMessage = `✅ ${ domain } resolves to ${ expectedIp } ` ;
108-
109-
110- setTimeout ( ( ) => {
111- this . $store . installer . currentStep = 4 ;
112- } , 1000 ) ;
113- } catch ( err ) {
114- console . error ( '❌ Domain verification failed:' , err ) ;
115+ } else {
116+ this . $store . installer . domain = '' ;
115117 this . $store . installer . domainVerified = false ;
116- this . domainStatusMessage = err . message || "Error verifying domain." ;
117- } finally {
118- this . isLoading = false ;
118+ this . $store . installer . currentStep ++ ;
119119 }
120- } else {
121- // No domain, just continue
122- this . $store . installer . domain = '' ;
123- this . $store . installer . domainVerified = false ;
124- this . $store . installer . currentStep = 4 ;
125120 }
126- }
127- } ;
128- }
129-
130- async function verify_domain ( domain , expectedIp ) {
131- const url = '/api/method/nano_press.api.check_domain_resolves_to_ip' ;
132- try {
133- const payload = {
134- domain : domain ,
135- expected_ip : expectedIp
136121 } ;
137-
138- const response = await frappe_call ( url , payload , "POST" ) ;
139-
140- if ( ! response ?. message ?. success ) {
141- throw new Error ( response ?. message ?. message || "Domain verification failed" ) ;
142- }
143-
144- const message = response . message . message ;
145- toast ( message , { type : "success" } ) ;
146- return response . message ;
147- } catch ( err ) {
148- const message = err . message || "Failed to verify domain" ;
149- toast ( message , { type : "danger" } ) ;
150- throw err ;
151122 }
152- }
153123</ script >
0 commit comments