feat: initialize gateway frontend with Vue 3, Vite, and Tailwind CSS
- Set up pnpm workspace with necessary dependencies including esbuild and vue-demi. - Create initial HTML structure for the frontend application. - Implement main App component with Vue Router integration. - Add global styles using Tailwind CSS and custom theme variables. - Define TypeScript environment for Vue components. - Create a default layout component for consistent page structure. - Set up Vue Router with a home view route. - Implement authentication store using Pinia for user management. - Create utility for tRPC client setup. - Develop HomeView component with login functionality and server status placeholder. - Configure TypeScript for Node with composite and strict options. - Set up Vite configuration for Vue and Tailwind CSS integration.
This commit is contained in:
@@ -46,14 +46,12 @@ const parseToken = (payload: Record<string, unknown>): KakaoOAuthToken => {
|
||||
|
||||
export class KakaoOAuthClient {
|
||||
private readonly restKey: string;
|
||||
private readonly adminKey?: string;
|
||||
private readonly redirectUri: string;
|
||||
private readonly oauthHost: string;
|
||||
private readonly apiHost: string;
|
||||
|
||||
constructor(config: KakaoOAuthConfig) {
|
||||
this.restKey = config.restKey;
|
||||
this.adminKey = config.adminKey;
|
||||
this.redirectUri = config.redirectUri;
|
||||
this.oauthHost = config.oauthHost ?? 'https://kauth.kakao.com';
|
||||
this.apiHost = config.apiHost ?? 'https://kapi.kakao.com';
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>삼국지 모의전투 HiDCHe - Gateway</title>
|
||||
</head>
|
||||
<body class="bg-black text-white">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,10 +4,29 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"dev": "node -e \"console.log('dev not configured')\"",
|
||||
"lint": "node -e \"console.log('lint not configured')\"",
|
||||
"test": "node -e \"console.log('test not configured')\"",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx --fix --ignore-path .gitignore",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sammo-ts/common": "workspace:*",
|
||||
"@trpc/client": "^11.8.1",
|
||||
"@trpc/server": "^11.8.1",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.26",
|
||||
"vue-router": "^4.6.4",
|
||||
"zod": "^4.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@vitejs/plugin-vue": "^6.0.3",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.3.0",
|
||||
"vue-tsc": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Global styles if needed */
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-sammo-dark: #1a1a1a;
|
||||
--color-sammo-gold: #ffd700;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-black text-gray-200;
|
||||
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif;
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col bg-black text-gray-200">
|
||||
<!-- Header / Navigation -->
|
||||
<header class="bg-zinc-900 border-b border-zinc-800 py-2 px-4">
|
||||
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
||||
<div class="flex items-center space-x-6">
|
||||
<h1 class="text-xl font-bold text-white">삼국지 모의전투 HiDCHe</h1>
|
||||
<nav class="hidden md:flex space-x-4 text-sm">
|
||||
<a href="#" class="hover:text-white">공지사항</a>
|
||||
<a href="#" class="hover:text-white">커뮤니티</a>
|
||||
<a href="#" class="hover:text-white">건의/제안/개발</a>
|
||||
<a href="#" class="hover:text-white">신고/문의</a>
|
||||
<a href="#" class="hover:text-white">자주 묻는 질문</a>
|
||||
<a href="#" class="hover:text-white">패치 내역</a>
|
||||
<a href="#" class="hover:text-white">Git Repo.</a>
|
||||
<a href="#" class="hover:text-white">위키</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="flex space-x-4 text-sm">
|
||||
<a href="#" class="hover:text-white">공식 오픈 톡</a>
|
||||
<a href="#" class="hover:text-white">잡담 오픈 톡</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-grow">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-zinc-900 border-t border-zinc-800 py-6 px-4 text-center text-xs text-zinc-500">
|
||||
<div class="space-x-4 mb-2">
|
||||
<a href="#" class="hover:text-zinc-300">개인정보처리방침</a>
|
||||
<a href="#" class="hover:text-zinc-300">이용약관</a>
|
||||
</div>
|
||||
<p>© 2023 • HideD</p>
|
||||
<p class="mt-1">크롬, 엣지, 파이어폭스에 최적화되어있습니다.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1 +1,12 @@
|
||||
export {};
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import './assets/main.css';
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import HomeView from '../views/HomeView.vue';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
// 추후 추가될 페이지들
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref(null);
|
||||
const isLoggedIn = ref(false);
|
||||
|
||||
function setUser(userData: any) {
|
||||
user.value = userData;
|
||||
isLoggedIn.value = !!userData;
|
||||
}
|
||||
|
||||
return { user, isLoggedIn, setUser };
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
||||
import type { AppRouter } from '../../../gateway-api/src/router';
|
||||
|
||||
export const trpc = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: '/api/trpc', // 실제 환경에 맞게 조정 필요
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import DefaultLayout from '../layouts/DefaultLayout.vue';
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
|
||||
const handleLogin = () => {
|
||||
console.log('Login attempt:', username.value);
|
||||
// tRPC 호출 로직이 들어갈 자리
|
||||
};
|
||||
|
||||
const handleJoin = () => {
|
||||
console.log('Join attempt');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultLayout>
|
||||
<div class="max-w-4xl mx-auto py-12 px-4 flex flex-col items-center space-y-12">
|
||||
<!-- Logo / Title -->
|
||||
<div class="text-center">
|
||||
<h2 class="text-4xl font-serif font-bold text-white tracking-widest">삼국지 모의전투 HiDCHe</h2>
|
||||
</div>
|
||||
|
||||
<!-- Login Box -->
|
||||
<div class="w-full max-w-md bg-zinc-800 border border-zinc-700 rounded shadow-2xl overflow-hidden">
|
||||
<div class="bg-zinc-700 px-6 py-2 text-center font-bold text-white border-b border-zinc-600">
|
||||
로그인
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="w-20 text-sm font-medium">계정명</label>
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
class="flex-grow bg-zinc-900 border border-zinc-600 rounded px-3 py-1.5 text-white focus:outline-none focus:border-blue-500"
|
||||
placeholder="계정명"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label class="w-20 text-sm font-medium">비밀번호</label>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
class="flex-grow bg-zinc-900 border border-zinc-600 rounded px-3 py-1.5 text-white focus:outline-none focus:border-blue-500"
|
||||
placeholder="비밀번호"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex space-x-2 pt-2">
|
||||
<button
|
||||
@click="handleJoin"
|
||||
class="flex-1 bg-yellow-600 hover:bg-yellow-500 text-black font-bold py-2 rounded transition-colors flex items-center justify-center space-x-2"
|
||||
>
|
||||
<span>가입 & 로그인</span>
|
||||
</button>
|
||||
<button
|
||||
@click="handleLogin"
|
||||
class="flex-[2] bg-blue-700 hover:bg-blue-600 text-white font-bold py-2 rounded transition-colors"
|
||||
>
|
||||
로그인
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Status Placeholder -->
|
||||
<div class="w-full max-w-2xl bg-zinc-900 border border-zinc-800 rounded-lg overflow-hidden shadow-xl">
|
||||
<div class="bg-zinc-800 px-4 py-2 border-b border-zinc-700 flex justify-between items-center">
|
||||
<span class="font-bold text-sm">체 현황</span>
|
||||
<span class="text-xs text-zinc-400">西紀 197年 7月 秋</span>
|
||||
</div>
|
||||
<div class="aspect-video bg-zinc-950 relative flex items-center justify-center">
|
||||
<!-- Map Placeholder -->
|
||||
<div class="text-zinc-700 text-lg italic">지도 이미지 및 현황 데이터 영역</div>
|
||||
|
||||
<!-- Example of a city dot if we wanted to mock it -->
|
||||
<div class="absolute bottom-4 right-4 text-[10px] text-blue-400">도시명 표기 끄기</div>
|
||||
</div>
|
||||
<div class="p-4 bg-black text-xs space-y-1 font-mono">
|
||||
<div class="flex items-start space-x-2">
|
||||
<span class="text-blue-400">◆</span>
|
||||
<span>197년 7월: [대회] 황제 수장의 명으로 전력전 대회가 개최됩니다! 천하의 영웅들을 모집하고 있습니다!</span>
|
||||
</div>
|
||||
<div class="flex items-start space-x-2">
|
||||
<span class="text-cyan-400">●</span>
|
||||
<span>197년 7월: [재난] 탐라 호토에 메뚜기 떼가 발생하여 도시가 황폐해지고 있습니다.</span>
|
||||
</div>
|
||||
<div class="flex items-start space-x-2">
|
||||
<span class="text-green-400">●</span>
|
||||
<span>197년 7월: [자금] 가을이 되어 봉록에 따라 군량이 지급됩니다.</span>
|
||||
</div>
|
||||
<div class="flex items-start space-x-2">
|
||||
<span class="text-red-400">●</span>
|
||||
<span>197년 4월: [재난] 남피, 무안에 홍수로 인해 피해가 급증하고 있습니다.</span>
|
||||
</div>
|
||||
<!-- ... more logs ... -->
|
||||
<div class="text-zinc-600 pt-2 italic text-center">최근 진행 상황 로그 (Placeholder)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Custom styles for the home view */
|
||||
</style>
|
||||
@@ -1,10 +1,31 @@
|
||||
{
|
||||
"extends": "../../tsconfig.paths.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"composite": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
},
|
||||
"include": ["src"]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import path from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
tailwindcss(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user