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