feat: 프론트엔드 프로젝트 우선 구조 재구성 (Phase 2-a)
- 데이터 계층 신설: types/project·useProject·project.store (프로젝트 CRUD·페이지네이션) - useTask/useMember/useActivity 경로를 /projects/:projectId/... 로 전환 - task·my-task·activity 타입/스토어 repoId/repoName → projectId/projectName - 알림 store 라우팅·문구 프로젝트화, 활동 타입 repo.linked/unlinked·project.* 반영 - 라우팅 /projects/... 전면 교체, / → /projects 리다이렉트 - 페이지 rename Repo*→Project* (List/Create/Detail/Settings/Members/Activity) - ProjectCreatePage: Gitea 저장소 폼 → 단순 프로젝트 생성 폼으로 재작성 - RepoHeader→ProjectHeader, RepoTabs→ProjectTabs, AppShell 네비 프로젝트화 - 죽은 파일 삭제: repo.store·useRepo·GiteaRepoBar Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,191 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
// Gitea 저장소 바 — 클론 주소 복사 + Gitea 웹에서 열기 (미연동 시 렌더 안 함)
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
interface Props {
|
||||
cloneUrl?: string | null
|
||||
htmlUrl?: string | null
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 둘 다 없으면 Gitea 미연동 — 표시하지 않음
|
||||
const connected = computed(() => !!(props.cloneUrl || props.htmlUrl))
|
||||
|
||||
const copied = ref(false)
|
||||
let copyTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// 클론 주소 클립보드 복사 — 성공 시 잠시 "복사됨" 표시
|
||||
async function copyClone() {
|
||||
if (!props.cloneUrl) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.cloneUrl)
|
||||
copied.value = true
|
||||
if (copyTimer) clearTimeout(copyTimer)
|
||||
copyTimer = setTimeout(() => (copied.value = false), 1500)
|
||||
} catch {
|
||||
// 클립보드 권한 없음 등 — 무시(주소는 화면에 노출되어 있음)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="connected"
|
||||
class="gitea-bar"
|
||||
>
|
||||
<span class="gb-ico">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 17H7A5 5 0 0 1 7 7h2" />
|
||||
<path d="M15 7h2a5 5 0 0 1 0 10h-2" />
|
||||
<path d="M8 12h8" />
|
||||
</svg>
|
||||
</span>
|
||||
<code
|
||||
v-if="cloneUrl"
|
||||
class="gb-url"
|
||||
:title="cloneUrl"
|
||||
>{{ cloneUrl }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="gb-url muted"
|
||||
>클론 주소 없음</span>
|
||||
|
||||
<button
|
||||
v-if="cloneUrl"
|
||||
class="gb-btn"
|
||||
type="button"
|
||||
:aria-label="copied ? '복사됨' : '클론 주소 복사'"
|
||||
@click="copyClone"
|
||||
>
|
||||
<svg
|
||||
v-if="!copied"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect
|
||||
x="9"
|
||||
y="9"
|
||||
width="13"
|
||||
height="13"
|
||||
rx="2"
|
||||
/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
{{ copied ? '복사됨' : '복사' }}
|
||||
</button>
|
||||
|
||||
<a
|
||||
v-if="htmlUrl"
|
||||
class="gb-btn open"
|
||||
:href="htmlUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
||||
<path d="M15 3h6v6M10 14 21 3" />
|
||||
</svg>
|
||||
Gitea에서 열기
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.gitea-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.gb-ico {
|
||||
color: #1b7a3d;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gb-ico svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
.gb-url {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.781rem;
|
||||
color: var(--text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.gb-url.muted {
|
||||
font-family: inherit;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.gb-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3125rem;
|
||||
height: 2rem;
|
||||
padding: 0 0.6875rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius);
|
||||
background: #fff;
|
||||
color: var(--text-2);
|
||||
font-family: inherit;
|
||||
font-size: 0.781rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
.gb-btn:hover {
|
||||
background: #f7f8fa;
|
||||
color: var(--text);
|
||||
}
|
||||
.gb-btn svg {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
}
|
||||
.gb-btn.open {
|
||||
border-color: var(--accent-border);
|
||||
color: var(--accent);
|
||||
background: var(--accent-weak);
|
||||
}
|
||||
.gb-btn.open:hover {
|
||||
background: var(--accent-weak);
|
||||
color: var(--accent);
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
// 저장소 헤더 카드 — 상세/멤버/활동/설정 화면이 공유
|
||||
// 프로젝트 헤더 카드 — 상세/멤버/활동/설정 화면이 공유
|
||||
import { computed } from 'vue'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import type { Repo, User } from '@/mock/relay.mock'
|
||||
import type { Project, User } from '@/mock/relay.mock'
|
||||
|
||||
interface Props {
|
||||
repo: Repo
|
||||
repo: Project
|
||||
/** 메타 우측 텍스트(예: "2시간 전 업데이트", "멤버 5명") */
|
||||
metaText: string
|
||||
/** 아바타 스택 override (기본: repo.members) */
|
||||
/** 아바타 스택 override (기본: project.members) */
|
||||
members?: User[]
|
||||
/** +N 표기 (기본: repo.moreCount) */
|
||||
/** +N 표기 (기본: project.moreCount) */
|
||||
moreCount?: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
@@ -72,39 +72,10 @@ const extra = computed(() => props.moreCount ?? props.repo.moreCount)
|
||||
{{ repo.visibility === 'private' ? '비공개' : '공개' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rh-slug">
|
||||
{{ repo.owner }} / {{ repo.id }}
|
||||
</div>
|
||||
<div class="rh-desc">
|
||||
{{ repo.desc }}
|
||||
</div>
|
||||
<div class="rh-meta">
|
||||
<span class="mi">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line
|
||||
x1="6"
|
||||
y1="3"
|
||||
x2="6"
|
||||
y2="15"
|
||||
/><circle
|
||||
cx="18"
|
||||
cy="6"
|
||||
r="3"
|
||||
/><circle
|
||||
cx="6"
|
||||
cy="18"
|
||||
r="3"
|
||||
/><path d="M18 9a9 9 0 0 1-9 9" />
|
||||
</svg>
|
||||
{{ repo.branch }}
|
||||
</span>
|
||||
<span class="mi avstack">
|
||||
<span
|
||||
v-for="m in avatars"
|
||||
@@ -3,7 +3,7 @@
|
||||
import { RouterLink } from 'vue-router'
|
||||
|
||||
interface Props {
|
||||
repoId: string
|
||||
projectId: string
|
||||
active: 'tasks' | 'members' | 'activity' | 'settings'
|
||||
taskCount: number
|
||||
memberCount: number
|
||||
@@ -16,26 +16,26 @@ defineProps<Props>()
|
||||
<template>
|
||||
<nav class="tabs">
|
||||
<RouterLink
|
||||
:to="`/repos/${repoId}`"
|
||||
:to="`/projects/${projectId}`"
|
||||
:class="{ active: active === 'tasks' }"
|
||||
>
|
||||
<span class="tb-label">업무</span> <span class="tb-count">{{ taskCount }}</span>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:to="`/repos/${repoId}/members`"
|
||||
:to="`/projects/${projectId}/members`"
|
||||
:class="{ active: active === 'members' }"
|
||||
>
|
||||
<span class="tb-label">멤버</span> <span class="tb-count">{{ memberCount }}</span>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:to="`/repos/${repoId}/activity`"
|
||||
:to="`/projects/${projectId}/activity`"
|
||||
:class="{ active: active === 'activity' }"
|
||||
>
|
||||
<span class="tb-label">활동</span>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-if="canManage"
|
||||
:to="`/repos/${repoId}/settings`"
|
||||
:to="`/projects/${projectId}/settings`"
|
||||
:class="{ active: active === 'settings' }"
|
||||
>
|
||||
<span class="tb-label">설정</span>
|
||||
Reference in New Issue
Block a user