diff --git a/src/App.vue b/src/App.vue
index 7e0b6e5..bfcdedd 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -7,6 +7,7 @@ import AppBottomNav from '@/components/layout/AppBottomNav.vue'
import LoginModal from '@/components/LoginModal.vue'
import SignupModal from '@/components/SignupModal.vue'
import WebOnlyNotice from '@/components/WebOnlyNotice.vue'
+import AppDialog from '@/components/ui/AppDialog.vue'
import { Capacitor } from '@capacitor/core'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
@@ -58,6 +59,9 @@ watch(() => route.fullPath, () => ui.closeSidebar())
+
+
+
diff --git a/src/composables/dialog.js b/src/composables/dialog.js
new file mode 100644
index 0000000..0f9e4d5
--- /dev/null
+++ b/src/composables/dialog.js
@@ -0,0 +1,57 @@
+import { reactive } from 'vue'
+
+// 앱 공용 인앱 다이얼로그 상태(싱글턴). 네이티브 alert/confirm/prompt 대체.
+// PC(Electron)에서 제목이 'sb_pt'로 뜨던 문제 + 웹/APK 일관 UI 제공.
+const state = reactive({
+ open: false,
+ type: 'alert', // 'alert' | 'confirm' | 'prompt'
+ title: '',
+ message: '',
+ okText: '확인',
+ cancelText: '취소',
+ danger: false,
+ inputValue: '',
+ placeholder: '',
+ _resolve: null,
+})
+
+function settle(result) {
+ const r = state._resolve
+ state._resolve = null
+ state.open = false
+ if (r) r(result)
+}
+
+function alert(message, { title = '알림', okText = '확인' } = {}) {
+ return new Promise((resolve) => {
+ Object.assign(state, { type: 'alert', title, message, okText, danger: false, _resolve: resolve, open: true })
+ })
+}
+
+function confirm(message, { title = '확인', okText = '확인', cancelText = '취소', danger = false } = {}) {
+ return new Promise((resolve) => {
+ Object.assign(state, { type: 'confirm', title, message, okText, cancelText, danger, _resolve: resolve, open: true })
+ })
+}
+
+function prompt(message, { title = '입력', okText = '확인', cancelText = '취소', defaultValue = '', placeholder = '' } = {}) {
+ return new Promise((resolve) => {
+ Object.assign(state, {
+ type: 'prompt', title, message, okText, cancelText,
+ inputValue: defaultValue, placeholder, danger: false, _resolve: resolve, open: true,
+ })
+ })
+}
+
+// AppDialog 컴포넌트에서 사용
+export const dialogState = state
+export function dialogConfirm() {
+ settle(state.type === 'prompt' ? state.inputValue : true)
+}
+export function dialogCancel() {
+ settle(state.type === 'prompt' ? null : false)
+}
+
+export function useDialog() {
+ return { alert, confirm, prompt }
+}
diff --git a/src/main.js b/src/main.js
index 18705f9..7c52f28 100644
--- a/src/main.js
+++ b/src/main.js
@@ -7,6 +7,14 @@ import App from './App.vue'
import router from './router'
import { setupCapacitor } from './capacitor'
import { useAuthStore } from './stores/auth'
+import { useDialog } from './composables/dialog'
+
+// 네이티브 alert 를 인앱 다이얼로그로 대체 (PC 'sb_pt' 제목 제거 + 웹/APK 일관).
+// confirm/prompt 는 동기 반환이라 오버라이드 불가 → 각 호출부에서 await dialog.confirm/prompt 사용.
+const dialog = useDialog()
+window.alert = (message) => {
+ dialog.alert(message == null ? '' : String(message))
+}
const app = createApp(App)
diff --git a/src/views/UsersView.vue b/src/views/UsersView.vue
index e64b92f..166301e 100644
--- a/src/views/UsersView.vue
+++ b/src/views/UsersView.vue
@@ -2,9 +2,11 @@
import { computed, onMounted, ref } from 'vue'
import { adminApi } from '@/api/adminApi'
import { useAuthStore } from '@/stores/auth'
+import { useDialog } from '@/composables/dialog'
import IconBtn from '@/components/ui/IconBtn.vue'
const auth = useAuthStore()
+const dialog = useDialog()
const myId = computed(() => auth.user?.id)
const members = ref([])
@@ -89,7 +91,7 @@ async function changeStatus(m, status) {
}
async function remove(m) {
- if (!confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요? 되돌릴 수 없습니다.`)) return
+ if (!(await dialog.confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요?\n되돌릴 수 없습니다.`, { title: '회원 삭제', danger: true }))) return
try {
await adminApi.removeMember(m.id)
members.value = members.value.filter((x) => x.id !== m.id)
diff --git a/src/views/account/AccountTagView.vue b/src/views/account/AccountTagView.vue
index b90f000..12e3b7e 100644
--- a/src/views/account/AccountTagView.vue
+++ b/src/views/account/AccountTagView.vue
@@ -3,9 +3,11 @@ import { onBeforeUnmount, onMounted, ref, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import Sortable from 'sortablejs'
import { accountApi } from '@/api/accountApi'
+import { useDialog } from '@/composables/dialog'
import IconBtn from '@/components/ui/IconBtn.vue'
const router = useRouter()
+const dialog = useDialog()
const tags = ref([])
const loading = ref(false)
@@ -79,7 +81,7 @@ async function saveTag(tag) {
}
async function removeTag(tag) {
- if (!confirm(`'${tag.name}' 태그를 삭제할까요? (내역에서 연결도 해제됩니다)`)) return
+ if (!(await dialog.confirm(`'${tag.name}' 태그를 삭제할까요?\n내역에서 연결도 해제됩니다.`, { title: '태그 삭제', danger: true }))) return
try {
await accountApi.removeTag(tag.id)
await load()
diff --git a/src/views/account/AccountView.vue b/src/views/account/AccountView.vue
index fcf2acd..054f3f3 100644
--- a/src/views/account/AccountView.vue
+++ b/src/views/account/AccountView.vue
@@ -1,12 +1,14 @@