1+ <template >
2+ <div class =" api-error-popup" >
3+ <div class =" overlay" @click =" onCancel" />
4+ <div class =" dialog" role =" dialog" aria-modal =" true" >
5+ <h3 class =" title" >{{ displayTitle }}</h3 >
6+ <p class =" message" v-html =" displayMessage" ></p >
7+ <div class =" buttons" >
8+ <button class =" btn cancel" @click =" onCancel" >{{ cancelLabel }}</button >
9+ <button class =" btn confirm" :disabled =" loading" @click =" onConfirmClick" >
10+ <span v-if =" loading" >{{ confirmingLabel }}</span >
11+ <span v-else >{{ confirmLabel }}</span >
12+ </button >
13+ </div >
14+ </div >
15+ </div >
16+ </template >
17+
18+ <script setup lang="ts">
19+ import { ref , computed } from " vue" ;
20+ import type { PropType , Ref } from " vue" ;
21+
22+ const props = defineProps ({
23+ title: String ,
24+ message: String ,
25+ // Optional reactive refs for live updates
26+ titleRef: { type: Object as PropType <Ref <string > | undefined > },
27+ messageRef: { type: Object as PropType <Ref <string > | undefined > },
28+ icon: { type: String as PropType <string >, default: " /assets/messages/Message-Default.png" },
29+ confirmLabel: { type: String as PropType <string >, default: " OK" },
30+ cancelLabel: { type: String as PropType <string >, default: " Cancel" },
31+ confirmingLabel: { type: String as PropType <string >, default: " Retrying..." },
32+ onConfirm: Function as PropType <() => Promise <any > | void >,
33+ close: Function as PropType <() => void >,
34+ });
35+
36+ const loading = ref (false );
37+
38+ const displayTitle = computed (() => {
39+ const tr = props .titleRef as Ref <string > | undefined ;
40+ return (tr ?.value ?? props .title ?? " " );
41+ });
42+
43+ const displayMessage = computed (() => {
44+ const mr = props .messageRef as Ref <string > | undefined ;
45+ return (mr ?.value ?? props .message ?? " " );
46+ });
47+
48+ async function onConfirmClick() {
49+ if (! props .onConfirm ) {
50+ props .close ?.();
51+ return ;
52+ }
53+ try {
54+ loading .value = true ;
55+ await props .onConfirm ();
56+ // on success the caller may close via close()
57+ } catch (e ) {
58+ // keep dialog open on failure – caller can decide behavior
59+ } finally {
60+ loading .value = false ;
61+ }
62+ }
63+
64+ function onCancel() {
65+ props .close ?.();
66+ }
67+ </script >
68+
69+ <style scoped>
70+ .api-error-popup {
71+ position : fixed ;
72+ inset : 0 ;
73+ display : flex ;
74+ align-items : center ;
75+ justify-content : center ;
76+ z-index : 10000 ;
77+ }
78+ .overlay {
79+ position : absolute ;
80+ inset : 0 ;
81+ background : rgba (0 , 0 , 0 , 0.45 );
82+ }
83+ .dialog {
84+ position : relative ;
85+ min-width : 300px ;
86+ max-width : 88% ;
87+ background : #fff ;
88+ padding : 18px 12px 0 12px ;
89+ border-radius : 10px ;
90+ box-shadow : 0 8px 30px rgba (0 , 0 , 0 , 0.25 );
91+ text-align : center ;
92+ }
93+ .title {
94+ font-size : 22px ;
95+ font-weight : 600 ;
96+ color : #222 ;
97+ margin : 6px 0 8px 0 ;
98+ }
99+ .message {
100+ color : #333 ;
101+ margin : 6px 10px 18px 10px ;
102+ line-height : 1.6 ;
103+ text-align : center ;
104+ }
105+ .message a {
106+ color : #0b6fff ;
107+ text-decoration : underline ;
108+ }
109+ .buttons {
110+ display : flex ;
111+ border-top : 1px solid #eee ;
112+ margin-top : 6px ;
113+ }
114+ .btn {
115+ flex : 1 1 50% ;
116+ padding : 14px 4px ;
117+ border : none ;
118+ background : transparent ;
119+ color : #0b6fff ;
120+ font-size : 17px ;
121+ }
122+ .btn.cancel {
123+ border-right : 1px solid #eee ;
124+ }
125+ .btn :disabled {
126+ opacity : 0.6 ;
127+ pointer-events : none ;}
128+ </style >
0 commit comments