Refactor Docker setup and enhance development environment

- Updated docker-compose.dev.yml to use Dockerfile.dev for backend builds and added HOST environment variable for frontend.
- Introduced Dockerfile.dev for streamlined backend development with Go.
- Enhanced development documentation to reflect changes in local setup and API proxying.
- Removed outdated frontend Dockerfile and adjusted frontend configuration for improved development experience.
This commit is contained in:
2026-02-28 23:53:20 +03:00
parent 5ffa10f8b7
commit 0466ff3087
12 changed files with 1203 additions and 1198 deletions

View File

@@ -1,128 +1,128 @@
<template>
<div class="h-screen flex flex-col bg-base-100 overflow-hidden">
<header class="navbar bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
<NuxtLink to="/" class="text-lg font-semibold hover:opacity-80 transition-all duration-200">{{ title }}</NuxtLink>
<div class="flex-1" />
<NuxtLink
v-if="!isLogin"
to="/"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path === '/' ? 'btn-primary' : ''"
>
<icons-icon-map />
Map
</NuxtLink>
<NuxtLink
v-if="!isLogin"
to="/profile"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path === '/profile' ? 'btn-primary' : ''"
>
<icons-icon-user />
Profile
</NuxtLink>
<NuxtLink
v-if="!isLogin && isAdmin"
to="/admin"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path.startsWith('/admin') ? 'btn-primary' : ''"
>
<icons-icon-shield />
Admin
</NuxtLink>
<button
v-if="!isLogin && me"
type="button"
class="btn btn-ghost btn-sm btn-error btn-outline gap-1.5 transition-all duration-200 hover:scale-105"
@click="doLogout"
>
<icons-icon-logout />
Logout
</button>
<label class="swap swap-rotate btn btn-ghost btn-sm transition-all duration-200 hover:scale-105">
<input type="checkbox" v-model="dark" @change="toggleTheme" />
<span class="swap-off"><icons-icon-sun /></span>
<span class="swap-on"><icons-icon-moon /></span>
</label>
<span v-if="live" class="badge badge-success badge-sm">Live</span>
</header>
<main class="flex-1 min-h-0 overflow-y-auto relative">
<slot />
</main>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const THEME_KEY = 'hnh-map-theme'
function getInitialDark(): boolean {
if (import.meta.client) {
const stored = localStorage.getItem(THEME_KEY)
if (stored === 'dark') return true
if (stored === 'light') return false
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
return false
}
const title = ref('HnH Map')
const dark = ref(false)
const live = ref(false)
const me = ref<{ username?: string; auths?: string[] } | null>(null)
const { isLoginPath } = useAppPaths()
const isLogin = computed(() => isLoginPath(route.path))
const isAdmin = computed(() => !!me.value?.auths?.includes('admin'))
async function loadMe() {
if (isLogin.value) return
try {
me.value = await useMapApi().me()
} catch {
me.value = null
}
}
async function loadConfig() {
if (isLogin.value) return
try {
const config = await useMapApi().getConfig()
if (config?.title) title.value = config.title
} catch (_) {}
}
onMounted(() => {
dark.value = getInitialDark()
const html = document.documentElement
html.setAttribute('data-theme', dark.value ? 'dark' : 'light')
})
watch(
() => route.path,
(path) => {
if (!isLoginPath(path)) loadMe().then(loadConfig)
},
{ immediate: true }
)
function toggleTheme() {
const html = document.documentElement
if (dark.value) {
html.setAttribute('data-theme', 'dark')
localStorage.setItem(THEME_KEY, 'dark')
} else {
html.setAttribute('data-theme', 'light')
localStorage.setItem(THEME_KEY, 'light')
}
}
async function doLogout() {
await useMapApi().logout()
await router.push('/login')
me.value = null
}
defineExpose({ setLive: (v: boolean) => { live.value = v } })
</script>
<template>
<div class="h-screen flex flex-col bg-base-100 overflow-hidden">
<header class="navbar bg-base-100/80 backdrop-blur-xl border-b border-base-300/50 px-4 gap-2 shrink-0">
<NuxtLink to="/" class="text-lg font-semibold hover:opacity-80 transition-all duration-200">{{ title }}</NuxtLink>
<div class="flex-1" />
<NuxtLink
v-if="!isLogin"
to="/"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path === '/' ? 'btn-primary' : ''"
>
<icons-icon-map />
Map
</NuxtLink>
<NuxtLink
v-if="!isLogin"
to="/profile"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path === '/profile' ? 'btn-primary' : ''"
>
<icons-icon-user />
Profile
</NuxtLink>
<NuxtLink
v-if="!isLogin && isAdmin"
to="/admin"
class="btn btn-ghost btn-sm gap-1.5 transition-all duration-200 hover:scale-105"
:class="route.path.startsWith('/admin') ? 'btn-primary' : ''"
>
<icons-icon-shield />
Admin
</NuxtLink>
<button
v-if="!isLogin && me"
type="button"
class="btn btn-ghost btn-sm btn-error btn-outline gap-1.5 transition-all duration-200 hover:scale-105"
@click="doLogout"
>
<icons-icon-logout />
Logout
</button>
<label class="swap swap-rotate btn btn-ghost btn-sm transition-all duration-200 hover:scale-105">
<input type="checkbox" v-model="dark" @change="toggleTheme" />
<span class="swap-off"><icons-icon-sun /></span>
<span class="swap-on"><icons-icon-moon /></span>
</label>
<span v-if="live" class="badge badge-success badge-sm">Live</span>
</header>
<main class="flex-1 min-h-0 overflow-y-auto relative">
<slot />
</main>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const THEME_KEY = 'hnh-map-theme'
function getInitialDark(): boolean {
if (import.meta.client) {
const stored = localStorage.getItem(THEME_KEY)
if (stored === 'dark') return true
if (stored === 'light') return false
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
return false
}
const title = ref('HnH Map')
const dark = ref(false)
const live = ref(false)
const me = ref<{ username?: string; auths?: string[] } | null>(null)
const { isLoginPath } = useAppPaths()
const isLogin = computed(() => isLoginPath(route.path))
const isAdmin = computed(() => !!me.value?.auths?.includes('admin'))
async function loadMe() {
if (isLogin.value) return
try {
me.value = await useMapApi().me()
} catch {
me.value = null
}
}
async function loadConfig() {
if (isLogin.value) return
try {
const config = await useMapApi().getConfig()
if (config?.title) title.value = config.title
} catch (_) {}
}
onMounted(() => {
dark.value = getInitialDark()
const html = document.documentElement
html.setAttribute('data-theme', dark.value ? 'dark' : 'light')
})
watch(
() => route.path,
(path) => {
if (!isLoginPath(path)) loadMe().then(loadConfig)
},
{ immediate: true }
)
function toggleTheme() {
const html = document.documentElement
if (dark.value) {
html.setAttribute('data-theme', 'dark')
localStorage.setItem(THEME_KEY, 'dark')
} else {
html.setAttribute('data-theme', 'light')
localStorage.setItem(THEME_KEY, 'light')
}
}
async function doLogout() {
await useMapApi().logout()
await router.push('/login')
me.value = null
}
defineExpose({ setLive: (v: boolean) => { live.value = v } })
</script>