From 5f3617e132d63629d9a8ac5cf680249efb431f47 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 11 Jun 2025 15:18:38 +0900 Subject: [PATCH] public files add --- public/index.html | 185 ++++++++++++++++++++++++++++++++++ public/manifest.json | 91 +++++++++++++++++ public/runtime-env.js | 177 +++++++++++++++++++++++++++++++++ public/sw.js | 224 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 677 insertions(+) create mode 100644 public/index.html create mode 100644 public/manifest.json create mode 100644 public/runtime-env.js create mode 100644 public/sw.js diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..dbe5779 --- /dev/null +++ b/public/index.html @@ -0,0 +1,185 @@ + + + + + + ₩ON - AI 마케팅 서비스 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
₩ON AI 마케팅 서비스를 불러오는 중...
+
+
+
+ + + + + \ No newline at end of file diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..5a7ed54 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,91 @@ +{ + "name": "₩ON - AI 마케팅 서비스", + "short_name": "₩ON", + "description": "소상공인을 위한 AI 기반 마케팅 솔루션", + "version": "1.0.0", + "lang": "ko", + "start_url": "/", + "display": "standalone", + "orientation": "portrait-primary", + "theme_color": "#1976D2", + "background_color": "#FAFAFA", + "categories": ["business", "productivity", "marketing"], + "icons": [ + { + "src": "/images/logo-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/logo-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/favicon.ico", + "sizes": "64x64", + "type": "image/x-icon" + } + ], + "screenshots": [ + { + "src": "/images/screenshot-1.png", + "sizes": "1280x720", + "type": "image/png", + "label": "메인 대시보드 화면" + }, + { + "src": "/images/screenshot-2.png", + "sizes": "1280x720", + "type": "image/png", + "label": "AI 콘텐츠 생성 화면" + } + ], + "shortcuts": [ + { + "name": "대시보드", + "short_name": "대시보드", + "description": "매출 현황 및 성과 확인", + "url": "/dashboard", + "icons": [ + { + "src": "/images/shortcut-dashboard.png", + "sizes": "96x96", + "type": "image/png" + } + ] + }, + { + "name": "콘텐츠 생성", + "short_name": "콘텐츠", + "description": "AI 마케팅 콘텐츠 생성", + "url": "/content/create", + "icons": [ + { + "src": "/images/shortcut-content.png", + "sizes": "96x96", + "type": "image/png" + } + ] + }, + { + "name": "매장 관리", + "short_name": "매장", + "description": "매장 및 메뉴 정보 관리", + "url": "/store", + "icons": [ + { + "src": "/images/shortcut-store.png", + "sizes": "96x96", + "type": "image/png" + } + ] + } + ], + "related_applications": [], + "prefer_related_applications": false, + "scope": "/", + "id": "won-ai-marketing" +} \ No newline at end of file diff --git a/public/runtime-env.js b/public/runtime-env.js new file mode 100644 index 0000000..605b1fa --- /dev/null +++ b/public/runtime-env.js @@ -0,0 +1,177 @@ +window.__runtime_config__ = { + // API Gateway URL (단일 진입점) + GATEWAY_URL: 'http://20.1.2.3', + + // 각 마이크로서비스별 URL (Ingress 라우팅) + MEMBER_URL: 'http://20.1.2.3/api/member', + AUTH_URL: 'http://20.1.2.3/api/auth', + STORE_URL: 'http://20.1.2.3/api/store', + CONTENT_URL: 'http://20.1.2.3/api/content', + RECOMMEND_URL: 'http://20.1.2.3/api/recommendation', + + // 애플리케이션 설정 + APP_VERSION: '1.0.0', + APP_NAME: '₩ON', + + // 환경 설정 + ENVIRONMENT: 'production', + DEBUG_MODE: false, + + // API 설정 + API_TIMEOUT: 30000, + RETRY_ATTEMPTS: 3, + + // 파일 업로드 설정 + MAX_FILE_SIZE: 10485760, // 10MB + ALLOWED_IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/webp'], + ALLOWED_VIDEO_TYPES: ['video/mp4', 'video/webm'], + + // 인증 설정 + TOKEN_REFRESH_MARGIN: 300, // 5분 전 토큰 갱신 + SESSION_TIMEOUT: 1800, // 30분 + + // UI 설정 + MOBILE_BREAKPOINT: 768, + TABLET_BREAKPOINT: 1024, + + // 페이지네이션 설정 + DEFAULT_PAGE_SIZE: 20, + MAX_PAGE_SIZE: 100, + + // 알림 설정 + NOTIFICATION_TIMEOUT: 5000, + MAX_NOTIFICATIONS: 5, + + // 캐시 설정 + CACHE_DURATION: 300000, // 5분 + + // 지도 API (필요한 경우) + // KAKAO_MAP_API_KEY: '', + // NAVER_MAP_API_KEY: '', + + // 소셜 로그인 (향후 확장) + // KAKAO_APP_KEY: '', + // NAVER_CLIENT_ID: '', + // GOOGLE_CLIENT_ID: '', + + // 분석 도구 (필요한 경우) + // GOOGLE_ANALYTICS_ID: '', + // HOTJAR_ID: '', + + // CDN 설정 + IMAGE_CDN_URL: 'http://20.1.2.3/images', + STATIC_CDN_URL: 'http://20.1.2.3/static', + + // 피처 플래그 + FEATURES: { + AI_RECOMMENDATION: true, + VOICE_INPUT: false, + DARK_MODE: true, + OFFLINE_MODE: false, + PUSH_NOTIFICATIONS: true, + REAL_TIME_UPDATES: true, + MULTI_STORE_SUPPORT: false, + ADVANCED_ANALYTICS: true + }, + + // 테마 설정 + THEME: { + PRIMARY_COLOR: '#1976D2', + SECONDARY_COLOR: '#FFC107', + SUCCESS_COLOR: '#4CAF50', + WARNING_COLOR: '#FF9800', + ERROR_COLOR: '#F44336', + INFO_COLOR: '#2196F3' + }, + + // 국제화 설정 + LOCALE: 'ko-KR', + TIMEZONE: 'Asia/Seoul', + CURRENCY: 'KRW', + + // 콘텐츠 생성 설정 + AI_CONTENT: { + MAX_REGENERATION_COUNT: 5, + CONTENT_TYPES: ['sns_post', 'poster', 'banner'], + PLATFORMS: ['instagram', 'naver_blog', 'facebook'], + TONE_OPTIONS: ['friendly', 'professional', 'humorous', 'elegant'], + EMOTION_LEVELS: ['calm', 'normal', 'passionate', 'exaggerated'] + }, + + // 메뉴 카테고리 기본값 + DEFAULT_MENU_CATEGORIES: ['면류', '밥류', '튀김', '분식', '음료', '디저트', '기타'], + + // 매장 업종 기본값 + BUSINESS_TYPES: [ + '한식', '중식', '일식', '양식', '분식', '치킨', '피자', '카페', + '베이커리', '아이스크림', '패스트푸드', '기타' + ], + + // 운영시간 기본값 + DEFAULT_OPERATING_HOURS: { + WEEKDAY: { open: '09:00', close: '22:00' }, + WEEKEND: { open: '10:00', close: '21:00' } + }, + + // 에러 메시지 + ERROR_MESSAGES: { + NETWORK_ERROR: '네트워크 연결을 확인해주세요.', + TIMEOUT_ERROR: '요청 시간이 초과되었습니다.', + UNAUTHORIZED: '로그인이 필요합니다.', + FORBIDDEN: '접근 권한이 없습니다.', + NOT_FOUND: '요청한 페이지를 찾을 수 없습니다.', + SERVER_ERROR: '서버 오류가 발생했습니다.', + VALIDATION_ERROR: '입력 정보를 확인해주세요.', + FILE_SIZE_ERROR: '파일 크기가 너무 큽니다.', + FILE_TYPE_ERROR: '지원하지 않는 파일 형식입니다.' + }, + + // 성공 메시지 + SUCCESS_MESSAGES: { + LOGIN_SUCCESS: '로그인이 완료되었습니다.', + LOGOUT_SUCCESS: '로그아웃이 완료되었습니다.', + REGISTER_SUCCESS: '회원가입이 완료되었습니다.', + UPDATE_SUCCESS: '정보가 수정되었습니다.', + DELETE_SUCCESS: '삭제가 완료되었습니다.', + SAVE_SUCCESS: '저장이 완료되었습니다.', + UPLOAD_SUCCESS: '업로드가 완료되었습니다.', + CONTENT_GENERATED: 'AI 콘텐츠가 생성되었습니다.' + }, + + // 확인 메시지 + CONFIRM_MESSAGES: { + DELETE_CONFIRM: '정말 삭제하시겠습니까?', + LOGOUT_CONFIRM: '로그아웃 하시겠습니까?', + CANCEL_CONFIRM: '작업을 취소하시겠습니까?', + OVERWRITE_CONFIRM: '기존 내용을 덮어쓰시겠습니까?' + } +}; + +// 환경별 설정 오버라이드 +if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + // 개발 환경 설정 + window.__runtime_config__.ENVIRONMENT = 'development'; + window.__runtime_config__.DEBUG_MODE = true; + window.__runtime_config__.GATEWAY_URL = 'http://localhost:8080'; + window.__runtime_config__.MEMBER_URL = 'http://localhost:8080/api/member'; + window.__runtime_config__.AUTH_URL = 'http://localhost:8080/api/auth'; + window.__runtime_config__.STORE_URL = 'http://localhost:8080/api/store'; + window.__runtime_config__.CONTENT_URL = 'http://localhost:8080/api/content'; + window.__runtime_config__.RECOMMEND_URL = 'http://localhost:8080/api/recommendation'; +} + +// 설정 유효성 검사 +if (!window.__runtime_config__.GATEWAY_URL) { + console.error('GATEWAY_URL이 설정되지 않았습니다.'); +} + +// 전역 함수로 설정 노출 +window.getConfig = function(key) { + return window.__runtime_config__[key]; +}; + +window.getFeature = function(featureName) { + return window.__runtime_config__.FEATURES[featureName] || false; +}; + +console.log('₩ON Runtime Configuration loaded:', window.__runtime_config__.ENVIRONMENT); diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..22e059f --- /dev/null +++ b/public/sw.js @@ -0,0 +1,224 @@ +// Service Worker for ₩ON AI Marketing Service +const CACHE_NAME = 'won-marketing-v1.0.0'; +const OFFLINE_URL = '/offline.html'; + +// 캐시할 정적 자원들 +const STATIC_CACHE_URLS = [ + '/', + '/offline.html', + '/manifest.json', + '/runtime-env.js', + '/images/logo.png', + '/images/logo-192.png', + '/images/logo-512.png', + 'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap', + 'https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css' +]; + +// 캐시하지 않을 URL 패턴 +const NO_CACHE_PATTERNS = [ + /\/api\//, + /\/auth\//, + /\/admin\// +]; + +// Service Worker 설치 +self.addEventListener('install', (event) => { + console.log('Service Worker 설치 중...'); + + event.waitUntil( + caches.open(CACHE_NAME) + .then((cache) => { + console.log('정적 자원 캐시 중...'); + return cache.addAll(STATIC_CACHE_URLS); + }) + .then(() => { + return self.skipWaiting(); + }) + ); +}); + +// Service Worker 활성화 +self.addEventListener('activate', (event) => { + console.log('Service Worker 활성화 중...'); + + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.map((cacheName) => { + if (cacheName !== CACHE_NAME) { + console.log('이전 캐시 삭제:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }).then(() => { + return self.clients.claim(); + }) + ); +}); + +// 네트워크 요청 처리 +self.addEventListener('fetch', (event) => { + const { request } = event; + const url = new URL(request.url); + + // 캐시하지 않을 패턴 체크 + const shouldNotCache = NO_CACHE_PATTERNS.some(pattern => pattern.test(url.pathname)); + + if (shouldNotCache) { + // API 요청 등은 캐시하지 않고 네트워크만 사용 + event.respondWith(fetch(request)); + return; + } + + // GET 요청만 캐시 전략 적용 + if (request.method === 'GET') { + event.respondWith( + caches.match(request) + .then((cachedResponse) => { + if (cachedResponse) { + // 캐시된 응답이 있으면 반환하고, 백그라운드에서 업데이트 + fetch(request) + .then((response) => { + if (response.status === 200) { + const responseClone = response.clone(); + caches.open(CACHE_NAME) + .then((cache) => { + cache.put(request, responseClone); + }); + } + }) + .catch(() => { + // 네트워크 오류 무시 + }); + + return cachedResponse; + } + + // 캐시된 응답이 없으면 네트워크에서 가져오기 + return fetch(request) + .then((response) => { + if (response.status === 200) { + const responseClone = response.clone(); + caches.open(CACHE_NAME) + .then((cache) => { + cache.put(request, responseClone); + }); + } + return response; + }) + .catch(() => { + // 네트워크 오류 시 오프라인 페이지 표시 + if (request.destination === 'document') { + return caches.match(OFFLINE_URL); + } + + // 이미지 요청 실패 시 기본 이미지 반환 + if (request.destination === 'image') { + return new Response( + '이미지 없음', + { headers: { 'Content-Type': 'image/svg+xml' } } + ); + } + + throw error; + }); + }) + ); + } else { + // POST, PUT, DELETE 등은 네트워크만 사용 + event.respondWith(fetch(request)); + } +}); + +// 백그라운드 동기화 +self.addEventListener('sync', (event) => { + if (event.tag === 'background-sync') { + console.log('백그라운드 동기화 실행'); + event.waitUntil(doBackgroundSync()); + } +}); + +// 푸시 알림 처리 +self.addEventListener('push', (event) => { + console.log('푸시 알림 수신:', event); + + const options = { + body: event.data ? event.data.text() : '새로운 알림이 있습니다.', + icon: '/images/logo-192.png', + badge: '/images/logo-192.png', + vibrate: [100, 50, 100], + data: { + dateOfArrival: Date.now(), + primaryKey: 1 + }, + actions: [ + { + action: 'explore', + title: '확인', + icon: '/images/checkmark.png' + }, + { + action: 'close', + title: '닫기', + icon: '/images/close.png' + } + ] + }; + + event.waitUntil( + self.registration.showNotification('₩ON 알림', options) + ); +}); + +// 알림 클릭 처리 +self.addEventListener('notificationclick', (event) => { + console.log('알림 클릭:', event); + + event.notification.close(); + + if (event.action === 'explore') { + event.waitUntil( + clients.openWindow('/') + ); + } else if (event.action === 'close') { + // 알림만 닫기 + } else { + // 기본 동작 - 앱 열기 + event.waitUntil( + clients.openWindow('/') + ); + } +}); + +// 백그라운드 동기화 함수 +async function doBackgroundSync() { + try { + // 오프라인 상태에서 저장된 데이터를 서버로 전송 + const pendingRequests = await getStoredRequests(); + + for (const request of pendingRequests) { + try { + await fetch(request.url, request.options); + await removeStoredRequest(request.id); + } catch (error) { + console.log('동기화 실패:', error); + } + } + } catch (error) { + console.log('백그라운드 동기화 오류:', error); + } +} + +// 저장된 요청 조회 +async function getStoredRequests() { + // IndexedDB나 Cache API를 사용하여 오프라인 요청 저장/조회 + return []; +} + +// 저장된 요청 삭제 +async function removeStoredRequest(id) { + // IndexedDB에서 요청 삭제 + return Promise.resolve(); +} \ No newline at end of file