@fuzionx/framework 0.1.45 → 0.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +29 -2
  2. package/cli/index.js +37 -8
  3. package/cli/templates/make/app-spa/controllers/AuthController.js +114 -0
  4. package/cli/templates/make/app-spa/controllers/HomeController.js +66 -0
  5. package/cli/templates/make/app-spa/controllers/PostController.js +191 -0
  6. package/cli/templates/make/app-spa/controllers/UserController.js +43 -0
  7. package/cli/templates/make/app-spa/public/css/style.css +1011 -0
  8. package/cli/templates/make/app-spa/routes/api.js +31 -0
  9. package/cli/templates/make/app-spa/routes/web.js +19 -0
  10. package/cli/templates/make/app-spa/services/AuthService.js +48 -0
  11. package/cli/templates/make/app-spa/services/PostService.js +372 -0
  12. package/cli/templates/make/app-spa/services/UserService.js +48 -0
  13. package/cli/templates/make/app-spa/views/default/errors/404.html +11 -0
  14. package/cli/templates/make/app-spa/views/default/errors/500.html +11 -0
  15. package/cli/templates/make/app-spa/views/default/layouts/main.html +34 -0
  16. package/cli/templates/make/app-spa/views/default/pages/home.html +22 -0
  17. package/cli/templates/make/app-spa/views/default/spa/index.html +13 -0
  18. package/cli/templates/make/app-spa/views/default/spa/package.json +20 -0
  19. package/cli/templates/make/app-spa/views/default/spa/src/App.vue +41 -0
  20. package/cli/templates/make/app-spa/views/default/spa/src/assets/landing.css +220 -0
  21. package/cli/templates/make/app-spa/views/default/spa/src/assets/style.css +1156 -0
  22. package/cli/templates/make/app-spa/views/default/spa/src/components/AlertDialog.vue +179 -0
  23. package/cli/templates/make/app-spa/views/default/spa/src/components/CodeBlock.vue +33 -0
  24. package/cli/templates/make/app-spa/views/default/spa/src/components/EditorToolbar.vue +54 -0
  25. package/cli/templates/make/app-spa/views/default/spa/src/components/FileUpload.vue +161 -0
  26. package/cli/templates/make/app-spa/views/default/spa/src/components/FlashMessage.vue +39 -0
  27. package/cli/templates/make/app-spa/views/default/spa/src/components/LanguageSwitcher.vue +108 -0
  28. package/cli/templates/make/app-spa/views/default/spa/src/components/Lightbox.vue +62 -0
  29. package/cli/templates/make/app-spa/views/default/spa/src/components/Navbar.vue +68 -0
  30. package/cli/templates/make/app-spa/views/default/spa/src/components/Pagination.vue +166 -0
  31. package/cli/templates/make/app-spa/views/default/spa/src/components/ToastContainer.vue +135 -0
  32. package/cli/templates/make/app-spa/views/default/spa/src/composables/useApi.js +129 -0
  33. package/cli/templates/make/app-spa/views/default/spa/src/composables/useClipboard.js +44 -0
  34. package/cli/templates/make/app-spa/views/default/spa/src/composables/useDate.js +73 -0
  35. package/cli/templates/make/app-spa/views/default/spa/src/composables/useDebounce.js +59 -0
  36. package/cli/templates/make/app-spa/views/default/spa/src/composables/useFlash.js +46 -0
  37. package/cli/templates/make/app-spa/views/default/spa/src/composables/useHeartbeat.js +45 -0
  38. package/cli/templates/make/app-spa/views/default/spa/src/composables/useLocalStorage.js +43 -0
  39. package/cli/templates/make/app-spa/views/default/spa/src/composables/useLocale.js +79 -0
  40. package/cli/templates/make/app-spa/views/default/spa/src/composables/useWebSocket.js +93 -0
  41. package/cli/templates/make/app-spa/views/default/spa/src/main.js +106 -0
  42. package/cli/templates/make/app-spa/views/default/spa/src/plugins/alert.js +96 -0
  43. package/cli/templates/make/app-spa/views/default/spa/src/plugins/toast.js +79 -0
  44. package/cli/templates/make/app-spa/views/default/spa/src/router/index.js +29 -0
  45. package/cli/templates/make/app-spa/views/default/spa/src/stores/auth.js +58 -0
  46. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardDetail.vue +169 -0
  47. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardForm.vue +192 -0
  48. package/cli/templates/make/app-spa/views/default/spa/src/views/BoardList.vue +129 -0
  49. package/cli/templates/make/app-spa/views/default/spa/src/views/ChatView.vue +317 -0
  50. package/cli/templates/make/app-spa/views/default/spa/src/views/FeaturesView.vue +242 -0
  51. package/cli/templates/make/app-spa/views/default/spa/src/views/HomeView.vue +215 -0
  52. package/cli/templates/make/app-spa/views/default/spa/src/views/Login.vue +82 -0
  53. package/cli/templates/make/app-spa/views/default/spa/src/views/Profile.vue +85 -0
  54. package/cli/templates/make/app-spa/views/default/spa/src/views/Register.vue +84 -0
  55. package/cli/templates/make/app-spa/views/default/spa/vite.config.js +28 -0
  56. package/cli/templates/make/app-spa/views/default/spa/yarn.lock +633 -0
  57. package/cli/templates/make/app-spa/ws/ChatHandler.js +138 -0
  58. package/cli/templates/make/app-ssr/controllers/AuthController.js +119 -0
  59. package/cli/templates/make/app-ssr/controllers/ChatController.js +15 -0
  60. package/cli/templates/make/app-ssr/controllers/FeaturesController.js +15 -0
  61. package/cli/templates/make/app-ssr/controllers/HomeController.js +21 -0
  62. package/cli/templates/make/app-ssr/controllers/PostController.js +214 -0
  63. package/cli/templates/make/app-ssr/controllers/UserController.js +48 -0
  64. package/cli/templates/make/app-ssr/public/css/fx-ui.css +43 -0
  65. package/cli/templates/make/app-ssr/public/css/landing.css +220 -0
  66. package/cli/templates/make/app-ssr/public/css/style.css +1011 -0
  67. package/cli/templates/make/app-ssr/public/js/fx-client.js +107 -0
  68. package/cli/templates/make/app-ssr/public/js/fx-ui.js +124 -0
  69. package/cli/templates/make/app-ssr/routes/web.js +46 -0
  70. package/cli/templates/make/app-ssr/services/AuthService.js +48 -0
  71. package/cli/templates/make/app-ssr/services/PostService.js +372 -0
  72. package/cli/templates/make/app-ssr/services/UserService.js +48 -0
  73. package/cli/templates/make/app-ssr/views/default/errors/404.html +11 -0
  74. package/cli/templates/make/app-ssr/views/default/errors/500.html +48 -0
  75. package/cli/templates/make/app-ssr/views/default/layouts/main.html +96 -0
  76. package/cli/templates/make/app-ssr/views/default/pages/board/form.html +240 -0
  77. package/cli/templates/make/app-ssr/views/default/pages/board/index.html +73 -0
  78. package/cli/templates/make/app-ssr/views/default/pages/board/show.html +148 -0
  79. package/cli/templates/make/app-ssr/views/default/pages/chat.html +288 -0
  80. package/cli/templates/make/app-ssr/views/default/pages/features.html +373 -0
  81. package/cli/templates/make/app-ssr/views/default/pages/home.html +258 -0
  82. package/cli/templates/make/app-ssr/views/default/pages/login.html +27 -0
  83. package/cli/templates/make/app-ssr/views/default/pages/profile.html +36 -0
  84. package/cli/templates/make/app-ssr/views/default/pages/register.html +35 -0
  85. package/cli/templates/make/app-ssr/views/default/partials/pagination.html +75 -0
  86. package/cli/templates/make/app-ssr/ws/ChatHandler.js +138 -0
  87. package/lib/core/Application.js +425 -138
  88. package/lib/core/Context.js +540 -236
  89. package/lib/middleware/auth.js +1 -1
  90. package/lib/middleware/csrf.js +1 -1
  91. package/lib/middleware/session.js +5 -4
  92. package/package.json +2 -2
@@ -0,0 +1,215 @@
1
+ <!--
2
+ HomeView.vue — 랜딩 페이지.
3
+
4
+ SSR home.html 완전 복제:
5
+ Hero (particles, badge, title, code block), Stats Bar,
6
+ Features Grid (6개), Architecture Layers, Comparison Table,
7
+ CTA Section, Footer.
8
+ -->
9
+ <template>
10
+ <!-- ── Hero Section ── -->
11
+ <section class="hero-section">
12
+ <div class="hero-particles">
13
+ <div class="particle p1"></div>
14
+ <div class="particle p2"></div>
15
+ <div class="particle p3"></div>
16
+ <div class="particle p4"></div>
17
+ <div class="particle p5"></div>
18
+ <div class="particle p6"></div>
19
+ </div>
20
+ <div class="container hero-container">
21
+ <div class="hero-badge">⚡ {{ t('home.hero_badge', 'Powered by Rust N-API') }}</div>
22
+ <h1 class="hero-title">
23
+ {{ t('home.hero_title_1', 'Build') }} <span class="gradient-text">{{ t('home.hero_title_2', 'Blazing Fast') }}</span><br>
24
+ {{ t('home.hero_title_3', 'Web Applications') }}
25
+ </h1>
26
+ <p class="hero-subtitle">
27
+ {{ t('home.hero_sub_1', 'Full-stack MVC framework for Node.js with a Rust-powered core engine.') }}<br>
28
+ {{ t('home.hero_sub_2', '500K+ requests per second. Zero-copy file handling. Native-speed crypto.') }}
29
+ </p>
30
+ <div class="hero-actions">
31
+ <router-link to="/features" class="btn btn-hero-primary">
32
+ <span>{{ t('home.btn_features', 'Explore Features') }}</span>
33
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M3 8h10M9 4l4 4-4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
34
+ </router-link>
35
+ <router-link to="/chat" class="btn btn-hero-outline">
36
+ <span>{{ t('home.btn_demo', 'Live Demo') }}</span>
37
+ </router-link>
38
+ </div>
39
+ <CodeBlock title="app.js" :code="heroCode" />
40
+ </div>
41
+ </section>
42
+
43
+ <!-- ── Stats Bar ── -->
44
+ <section class="stats-bar">
45
+ <div class="container">
46
+ <div class="stats-bar-grid">
47
+ <div class="stat-bar-item">
48
+ <span class="stat-bar-number">500K+</span>
49
+ <span class="stat-bar-label">{{ t('home.stat_rps', 'Requests/sec') }}</span>
50
+ </div>
51
+ <div class="stat-bar-divider"></div>
52
+ <div class="stat-bar-item">
53
+ <span class="stat-bar-number">3</span>
54
+ <span class="stat-bar-label">{{ t('home.stat_db', 'Database Drivers') }}</span>
55
+ </div>
56
+ <div class="stat-bar-divider"></div>
57
+ <div class="stat-bar-item">
58
+ <span class="stat-bar-number">0</span>
59
+ <span class="stat-bar-label">{{ t('home.stat_zero_copy', 'Memory Copy Uploads') }}</span>
60
+ </div>
61
+ <div class="stat-bar-divider"></div>
62
+ <div class="stat-bar-item">
63
+ <span class="stat-bar-number">21</span>
64
+ <span class="stat-bar-label">{{ t('home.stat_modules', 'Core Modules') }}</span>
65
+ </div>
66
+ </div>
67
+ </div>
68
+ </section>
69
+
70
+ <!-- ── Core Features Grid ── -->
71
+ <section class="section">
72
+ <div class="container">
73
+ <div class="section-header">
74
+ <h2 class="section-title">{{ t('home.feat_title_1', 'Everything You Need.') }}<br><span class="gradient-text">{{ t('home.feat_title_2', "Nothing You Don't.") }}</span></h2>
75
+ <p class="section-subtitle">{{ t('home.feat_subtitle', 'A production-ready framework that ships with batteries included — powered by Rust under the hood.') }}</p>
76
+ </div>
77
+ <div class="features-showcase">
78
+ <div v-for="feat in features" :key="feat.icon" class="feature-showcase-card">
79
+ <div class="feature-icon-wrap"><span class="feature-icon">{{ feat.icon }}</span></div>
80
+ <h3>{{ feat.title }}</h3>
81
+ <p>{{ feat.desc }}</p>
82
+ <span :class="['feature-tag', feat.tagClass]">{{ feat.tag }}</span>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </section>
87
+
88
+ <!-- ── Architecture ── -->
89
+ <section class="section section-dark">
90
+ <div class="container">
91
+ <div class="section-header">
92
+ <h2 class="section-title">{{ t('home.arch_title_1', 'Layered') }} <span class="gradient-text">{{ t('home.arch_title_2', 'Architecture') }}</span></h2>
93
+ <p class="section-subtitle">{{ t('home.arch_subtitle', 'Three-tier design separating concerns for maximum performance and developer ergonomics.') }}</p>
94
+ </div>
95
+ <div class="arch-layers">
96
+ <div class="arch-layer arch-layer-top">
97
+ <div class="arch-layer-badge">{{ t('home.arch_you_write', 'You Write') }}</div>
98
+ <h4>@fuzionx/framework</h4>
99
+ <p>{{ t('home.arch_framework_desc', 'Controllers, Services, Models, Middleware, WebSocket Handlers, Jobs — Laravel-style MVC with full DI container') }}</p>
100
+ </div>
101
+ <div class="arch-connector">
102
+ <svg width="24" height="30" viewBox="0 0 24 30"><path d="M12 0v24M6 18l6 6 6-6" stroke="rgba(102,126,234,0.6)" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
103
+ </div>
104
+ <div class="arch-layer arch-layer-mid">
105
+ <div class="arch-layer-badge">{{ t('home.arch_engine', 'Engine') }}</div>
106
+ <h4>@fuzionx/core</h4>
107
+ <p>{{ t('home.arch_core_desc', 'Express-level HTTP engine wrapping the Rust bridge. Routing, request/response lifecycle, WebSocket server.') }}</p>
108
+ </div>
109
+ <div class="arch-connector">
110
+ <svg width="24" height="30" viewBox="0 0 24 30"><path d="M12 0v24M6 18l6 6 6-6" stroke="rgba(118,75,162,0.6)" stroke-width="2" fill="none" stroke-linecap="round"/></svg>
111
+ </div>
112
+ <div class="arch-layer arch-layer-bottom">
113
+ <div class="arch-layer-badge">{{ t('home.arch_native', 'Native') }}</div>
114
+ <h4>@fuzionx/bridge</h4>
115
+ <p>{{ t('home.arch_bridge_desc', 'Rust N-API binary — libuv Fusion HTTP, native crypto, media processing, file I/O. Zero-copy where possible.') }}</p>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </section>
120
+
121
+ <!-- ── Comparison Table ── -->
122
+ <section class="section">
123
+ <div class="container">
124
+ <div class="section-header">
125
+ <h2 class="section-title">{{ t('home.why_title_1', 'Why') }} <span class="gradient-text">FuzionX</span>?</h2>
126
+ <p class="section-subtitle">{{ t('home.why_subtitle', 'See how FuzionX stacks up against popular Node.js frameworks.') }}</p>
127
+ </div>
128
+ <div class="comparison-table-wrap">
129
+ <table class="comparison-table">
130
+ <thead>
131
+ <tr>
132
+ <th>{{ t('home.cmp_feature', 'Feature') }}</th>
133
+ <th class="highlight-col">FuzionX</th>
134
+ <th>Express</th>
135
+ <th>NestJS</th>
136
+ </tr>
137
+ </thead>
138
+ <tbody>
139
+ <tr v-for="row in comparisonRows" :key="row.feature">
140
+ <td>{{ row.feature }}</td>
141
+ <td class="highlight-col"><span class="check">✓</span> {{ row.fuzionx }}</td>
142
+ <td><span :class="row.express.startsWith('✗') ? 'cross' : ''">{{ row.express }}</span></td>
143
+ <td><span :class="row.nestjs.startsWith('✗') ? 'cross' : ''">{{ row.nestjs }}</span></td>
144
+ </tr>
145
+ </tbody>
146
+ </table>
147
+ </div>
148
+ </div>
149
+ </section>
150
+
151
+ <!-- ── CTA ── -->
152
+ <section class="cta-section">
153
+ <div class="container">
154
+ <div class="cta-card glass-card">
155
+ <h2 class="cta-title">{{ t('home.cta_title_1', 'Ready to Build') }} <span class="gradient-text">{{ t('home.cta_title_2', 'Something Fast') }}</span>?</h2>
156
+ <p class="cta-subtitle">{{ t('home.cta_subtitle', 'Get started with FuzionX in seconds. One command, zero configuration.') }}</p>
157
+ <div class="cta-install">
158
+ <code>npx create-fuzionx my-app</code>
159
+ </div>
160
+ <div class="hero-actions" style="margin-top: 1.5rem;">
161
+ <router-link to="/features" class="btn btn-hero-primary"><span>{{ t('home.btn_all_features', 'View All Features') }}</span></router-link>
162
+ <router-link to="/chat" class="btn btn-hero-outline"><span>{{ t('home.btn_try_chat', 'Try Chat Demo') }}</span></router-link>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </section>
167
+
168
+ <!-- ── Footer ── -->
169
+ <footer class="site-footer">
170
+ <div class="container">
171
+ <div class="footer-content">
172
+ <span class="footer-brand">FuzionX</span>
173
+ <span class="footer-text">{{ t('home.footer', 'Rust-powered Node.js framework for the modern web.') }}</span>
174
+ </div>
175
+ </div>
176
+ </footer>
177
+ </template>
178
+
179
+ <script setup>
180
+ import { computed } from 'vue';
181
+ import { useLocale } from '../composables/useLocale.js';
182
+ import CodeBlock from '../components/CodeBlock.vue';
183
+
184
+ const { t } = useLocale();
185
+
186
+ /** Hero 코드 블록 HTML (줄바꿈 보장) */
187
+ const heroCode = [
188
+ `<span class="code-kw">import</span> { Application } <span class="code-kw">from</span> <span class="code-str">'@fuzionx/framework'</span>;`,
189
+ `<span class="code-kw">const</span> app = <span class="code-kw">new</span> <span class="code-fn">Application</span>({ configPath: <span class="code-str">'./fuzionx.yaml'</span> });`,
190
+ `<span class="code-kw">await</span> app.<span class="code-fn">boot</span>();`,
191
+ `<span class="code-kw">await</span> app.<span class="code-fn">listen</span>(); <span class="code-cmt">// 🚀 500K+ RPS ready</span>`,
192
+ ].join('\n');
193
+
194
+ /** Core Features 그리드 데이터 — SSR home.html 6개 카드 */
195
+ const features = computed(() => [
196
+ { icon: '⚡', title: t('home.feat_bridge', 'Rust N-API Bridge'), desc: t('home.feat_bridge_desc', 'HTTP server, session management, cryptography, and file processing at native speed. No JavaScript overhead for core operations.'), tag: 'Rust', tagClass: 'tag-rust' },
197
+ { icon: '🗄️', title: t('home.feat_orm', 'Multi-DB ORM'), desc: t('home.feat_orm_desc', 'One unified API for MariaDB, PostgreSQL, and MongoDB. Model-based schema sync replaces migration files.'), tag: 'Node.js', tagClass: 'tag-node' },
198
+ { icon: '🔌', title: t('home.feat_ws', 'Real-time WebSocket'), desc: t('home.feat_ws_desc', 'Namespace routing, rooms, event-based handlers, and multi-server Hub broadcasting built into the framework.'), tag: 'WebSocket', tagClass: 'tag-ws' },
199
+ { icon: '🎨', title: t('home.feat_ssr', 'SSR + SPA Hybrid'), desc: t('home.feat_ssr_desc', 'Tera template engine for SSR pages alongside Vue.js SPA — run both in a single project with domain-based routing.'), tag: 'Hybrid', tagClass: 'tag-node' },
200
+ { icon: '🔐', title: t('home.feat_security', 'Enterprise Security'), desc: t('home.feat_security_desc', 'Native bcrypt/argon2 hashing, AES-256-GCM encryption, ASP wire protocol, CSRF protection, and rate limiting.'), tag: 'Rust', tagClass: 'tag-rust' },
201
+ { icon: '⏰', title: t('home.feat_queue', 'Task Queue + Workers'), desc: t('home.feat_queue_desc', 'Scheduled jobs (cron), async task queues with retry logic, and WorkerPool for CPU-heavy operations in isolated threads.'), tag: 'Node.js', tagClass: 'tag-node' },
202
+ ]);
203
+
204
+ /** Comparison Table 데이터 — SSR home.html 테이블 */
205
+ const comparisonRows = computed(() => [
206
+ { feature: t('home.cmp_http', 'HTTP Engine'), fuzionx: 'Rust N-API', express: 'JavaScript', nestjs: 'JavaScript' },
207
+ { feature: t('home.cmp_orm', 'ORM Built-in'), fuzionx: 'Multi-DB', express: `✗ ${t('home.cmp_3rd_party', '3rd party')}`, nestjs: '✗ TypeORM plugin' },
208
+ { feature: 'WebSocket', fuzionx: 'Native + Hub', express: '✗ socket.io', nestjs: '✗ Gateway plugin' },
209
+ { feature: t('home.cmp_upload', 'File Upload'), fuzionx: 'Zero-copy Rust', express: 'multer (JS memory)', nestjs: 'multer (JS memory)' },
210
+ { feature: 'Crypto/Hash', fuzionx: 'Native Rust', express: 'Node.js crypto', nestjs: 'Node.js crypto' },
211
+ { feature: t('home.cmp_queue', 'Task Queue'), fuzionx: t('home.cmp_builtin', 'Built-in'), express: '✗ bull/agenda', nestjs: '✗ bull plugin' },
212
+ { feature: t('home.cmp_schema', 'Schema Sync'), fuzionx: t('home.cmp_model_based', 'Model-based'), express: '✗ N/A', nestjs: '✗ Migrations' },
213
+ { feature: 'i18n', fuzionx: t('home.cmp_builtin', 'Built-in'), express: '✗ i18next', nestjs: '✗ nestjs-i18n' },
214
+ ]);
215
+ </script>
@@ -0,0 +1,82 @@
1
+ <!--
2
+ Login.vue — 로그인 페이지.
3
+
4
+ SSR login.html 완전 복제:
5
+ Glass card 인증 폼 (이메일, 비밀번호).
6
+ Pinia auth store로 상태 관리.
7
+ -->
8
+ <template>
9
+ <div class="auth-container">
10
+ <div class="glass-card auth-card">
11
+ <div class="auth-logo">FuzionX</div>
12
+ <p class="auth-subtitle">{{ t('page.login', '로그인') }}</p>
13
+
14
+ <form class="auth-form" @submit.prevent="handleLogin">
15
+ <div class="form-group">
16
+ <label for="email">{{ t('label.email', '이메일') }}</label>
17
+ <input
18
+ v-model="form.email"
19
+ type="email"
20
+ id="email"
21
+ required
22
+ placeholder="name@example.com"
23
+ />
24
+ </div>
25
+ <div class="form-group">
26
+ <label for="password">{{ t('label.password', '비밀번호') }}</label>
27
+ <input
28
+ v-model="form.password"
29
+ type="password"
30
+ id="password"
31
+ required
32
+ />
33
+ </div>
34
+
35
+ <button type="submit" class="btn btn-primary btn-full" :disabled="loading">
36
+ {{ loading ? '...' : t('btn.login', '로그인') }}
37
+ </button>
38
+ </form>
39
+
40
+ <div class="auth-footer">
41
+ {{ t('auth.no_account', '계정이 없으신가요?') }}
42
+ <router-link to="/register">{{ t('btn.register', '회원가입') }}</router-link>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup>
49
+ import { ref, reactive } from 'vue';
50
+ import { useRouter } from 'vue-router';
51
+ import { useApi } from '../composables/useApi.js';
52
+ import { useAuthStore } from '../stores/auth.js';
53
+ import { useLocale } from '../composables/useLocale.js';
54
+ import { useToast } from '../plugins/toast.js';
55
+
56
+ const router = useRouter();
57
+ const api = useApi();
58
+ const authStore = useAuthStore();
59
+ const { t } = useLocale();
60
+ const toast = useToast();
61
+
62
+ const form = reactive({ email: '', password: '' });
63
+ const loading = ref(false);
64
+
65
+ /** 로그인 처리 */
66
+ async function handleLogin() {
67
+ loading.value = true;
68
+ try {
69
+ const res = await api.post('/api/auth/login', form);
70
+ if (res?.user) {
71
+ authStore.setUser(res.user);
72
+ router.push('/');
73
+ } else {
74
+ toast.error(res?.message || t('error.login_failed', '로그인에 실패했습니다.'));
75
+ }
76
+ } catch (e) {
77
+ toast.error(t('error.network', '네트워크 오류가 발생했습니다.'));
78
+ } finally {
79
+ loading.value = false;
80
+ }
81
+ }
82
+ </script>
@@ -0,0 +1,85 @@
1
+ <!--
2
+ Profile.vue — 프로필 수정 페이지.
3
+
4
+ SSR profile.html 완전 복제:
5
+ Glass card 폼 (이름, 이메일, 새 비밀번호, 비밀번호 확인).
6
+ 인증 필요 (auth required).
7
+ -->
8
+ <template>
9
+ <div class="container">
10
+ <div class="glass-card">
11
+ <h1 class="page-title">{{ t('profile.title', '프로필 수정') }}</h1>
12
+
13
+ <form class="form" @submit.prevent="handleUpdate">
14
+ <div class="form-group">
15
+ <label for="name">{{ t('label.name', '이름') }}</label>
16
+ <input v-model="form.name" type="text" id="name" required />
17
+ </div>
18
+ <div class="form-group">
19
+ <label for="email">{{ t('label.email', '이메일') }}</label>
20
+ <input v-model="form.email" type="email" id="email" required />
21
+ </div>
22
+ <div class="form-group">
23
+ <label for="password">{{ t('profile.new_password', '새 비밀번호 (변경 시에만)') }}</label>
24
+ <input v-model="form.password" type="password" id="password" />
25
+ </div>
26
+ <div class="form-group">
27
+ <label for="password_confirm">{{ t('label.password_confirm', '비밀번호 확인') }}</label>
28
+ <input v-model="form.password_confirm" type="password" id="password_confirm" />
29
+ </div>
30
+
31
+ <button type="submit" class="btn btn-primary" :disabled="loading">
32
+ {{ loading ? '...' : t('btn.save', '저장') }}
33
+ </button>
34
+ </form>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup>
40
+ import { ref, reactive, onMounted } from 'vue';
41
+ import { useApi } from '../composables/useApi.js';
42
+ import { useAuthStore } from '../stores/auth.js';
43
+ import { useLocale } from '../composables/useLocale.js';
44
+ import { useToast } from '../plugins/toast.js';
45
+
46
+ const api = useApi();
47
+ const authStore = useAuthStore();
48
+ const { t } = useLocale();
49
+ const toast = useToast();
50
+
51
+ const form = reactive({ name: '', email: '', password: '', password_confirm: '' });
52
+ const loading = ref(false);
53
+
54
+ /** 초기 로드 — 현재 사용자 정보 채우기 */
55
+ onMounted(() => {
56
+ if (authStore.user) {
57
+ form.name = authStore.user.name || '';
58
+ form.email = authStore.user.email || '';
59
+ }
60
+ });
61
+
62
+ /** 프로필 업데이트 처리 */
63
+ async function handleUpdate() {
64
+ if (form.password && form.password !== form.password_confirm) {
65
+ toast.error(t('error.password_mismatch', '비밀번호가 일치하지 않습니다.'));
66
+ return;
67
+ }
68
+ loading.value = true;
69
+ try {
70
+ const res = await api.put('/api/user/profile', form);
71
+ if (res?.user) {
72
+ authStore.setUser(res.user);
73
+ toast.success(t('profile.updated', '프로필이 업데이트되었습니다.'));
74
+ form.password = '';
75
+ form.password_confirm = '';
76
+ } else {
77
+ toast.error(res?.message || t('error.update_failed', '업데이트에 실패했습니다.'));
78
+ }
79
+ } catch (e) {
80
+ toast.error(t('error.network', '네트워크 오류가 발생했습니다.'));
81
+ } finally {
82
+ loading.value = false;
83
+ }
84
+ }
85
+ </script>
@@ -0,0 +1,84 @@
1
+ <!--
2
+ Register.vue — 회원가입 페이지.
3
+
4
+ SSR register.html 완전 복제:
5
+ Glass card 인증 폼 (이름, 이메일, 비밀번호, 비밀번호 확인).
6
+ -->
7
+ <template>
8
+ <div class="auth-container">
9
+ <div class="glass-card auth-card">
10
+ <div class="auth-logo">FuzionX</div>
11
+ <p class="auth-subtitle">{{ t('page.register', '회원가입') }}</p>
12
+
13
+ <form class="auth-form" @submit.prevent="handleRegister">
14
+ <div class="form-group">
15
+ <label for="name">{{ t('label.name', '이름') }}</label>
16
+ <input v-model="form.name" type="text" id="name" required />
17
+ </div>
18
+ <div class="form-group">
19
+ <label for="email">{{ t('label.email', '이메일') }}</label>
20
+ <input v-model="form.email" type="email" id="email" required placeholder="name@example.com" />
21
+ </div>
22
+ <div class="form-group">
23
+ <label for="password">{{ t('label.password', '비밀번호') }}</label>
24
+ <input v-model="form.password" type="password" id="password" required />
25
+ </div>
26
+ <div class="form-group">
27
+ <label for="password_confirm">{{ t('label.password_confirm', '비밀번호 확인') }}</label>
28
+ <input v-model="form.password_confirm" type="password" id="password_confirm" required />
29
+ </div>
30
+
31
+ <button type="submit" class="btn btn-primary btn-full" :disabled="loading">
32
+ {{ loading ? '...' : t('btn.register', '회원가입') }}
33
+ </button>
34
+ </form>
35
+
36
+ <div class="auth-footer">
37
+ {{ t('auth.has_account', '이미 계정이 있으신가요?') }}
38
+ <router-link to="/login">{{ t('btn.login', '로그인') }}</router-link>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup>
45
+ import { ref, reactive } from 'vue';
46
+ import { useRouter } from 'vue-router';
47
+ import { useApi } from '../composables/useApi.js';
48
+ import { useAuthStore } from '../stores/auth.js';
49
+ import { useLocale } from '../composables/useLocale.js';
50
+ import { useToast } from '../plugins/toast.js';
51
+ import { useAlert } from '../plugins/alert.js';
52
+
53
+ const router = useRouter();
54
+ const api = useApi();
55
+ const authStore = useAuthStore();
56
+ const { t } = useLocale();
57
+ const toast = useToast();
58
+ const alert = useAlert();
59
+
60
+ const form = reactive({ name: '', email: '', password: '', password_confirm: '' });
61
+ const loading = ref(false);
62
+
63
+ /** 회원가입 처리 */
64
+ async function handleRegister() {
65
+ if (form.password !== form.password_confirm) {
66
+ toast.error(t('error.password_mismatch', '비밀번호가 일치하지 않습니다.'));
67
+ return;
68
+ }
69
+ loading.value = true;
70
+ try {
71
+ const res = await api.post('/api/auth/register', form);
72
+ if (res?.user) {
73
+ await alert.show(t('alert.success', '성공'), t('register.success', '회원가입이 완료되었습니다.'));
74
+ router.push('/login');
75
+ } else {
76
+ toast.error(res?.message || t('error.register_failed', '회원가입에 실패했습니다.'));
77
+ }
78
+ } catch (e) {
79
+ toast.error(t('error.network', '네트워크 오류가 발생했습니다.'));
80
+ } finally {
81
+ loading.value = false;
82
+ }
83
+ }
84
+ </script>
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from "vite";
2
+ import vue from "@vitejs/plugin-vue";
3
+ import path from "node:path";
4
+
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "src"),
10
+ },
11
+ },
12
+ server: {
13
+ port: 5173,
14
+ proxy: {
15
+ "/api": "http://127.0.0.1:49080",
16
+ "/wasm": "http://127.0.0.1:49080",
17
+ },
18
+ },
19
+ build: {
20
+ outDir: path.resolve(__dirname, "../../../../public/dist"),
21
+ emptyOutDir: true,
22
+ rollupOptions: {
23
+ output: {
24
+ entryFileNames: "assets/main.js",
25
+ },
26
+ },
27
+ },
28
+ });