릴리스: v1.3.69 관리자 티어표 집계와 아이템 UI 정리
This commit is contained in:
@@ -2026,6 +2026,50 @@ async function listAdminTierLists({ queryText = '', page = 1, limit = 50, curren
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeAdminTierLists({ queryText = '', gameId = '' } = {}) {
|
||||
const hasQuery = !!(queryText || '').trim()
|
||||
const hasGameId = !!(gameId || '').trim()
|
||||
const search = `%${(queryText || '').trim()}%`
|
||||
const whereParts = []
|
||||
const params = []
|
||||
|
||||
if (hasGameId) {
|
||||
whereParts.push('t.game_id = ?')
|
||||
params.push((gameId || '').trim())
|
||||
}
|
||||
|
||||
if (hasQuery) {
|
||||
whereParts.push(`(
|
||||
t.title LIKE ?
|
||||
OR g.name LIKE ?
|
||||
OR g.id LIKE ?
|
||||
OR u.email LIKE ?
|
||||
OR u.nickname LIKE ?
|
||||
)`)
|
||||
params.push(search, search, search, search, search)
|
||||
}
|
||||
|
||||
const whereClause = whereParts.length ? `WHERE ${whereParts.join(' AND ')}` : ''
|
||||
const rows = await query(
|
||||
`
|
||||
SELECT t.is_public
|
||||
FROM tierlists t
|
||||
INNER JOIN users u ON u.id = t.author_id
|
||||
INNER JOIN games g ON g.id = t.game_id
|
||||
${whereClause}
|
||||
`,
|
||||
params
|
||||
)
|
||||
|
||||
const total = rows.length
|
||||
const publicCount = rows.filter((row) => Number(row.is_public) === 1).length
|
||||
return {
|
||||
total,
|
||||
publicCount,
|
||||
privateCount: Math.max(0, total - publicCount),
|
||||
}
|
||||
}
|
||||
|
||||
async function findTierListById(id, currentUserId = '') {
|
||||
const rows = await query(
|
||||
`
|
||||
@@ -2408,6 +2452,7 @@ module.exports = {
|
||||
listFavoriteTierLists,
|
||||
listUserTierLists,
|
||||
listAdminTierLists,
|
||||
summarizeAdminTierLists,
|
||||
findTierListById,
|
||||
favoriteTierList,
|
||||
unfavoriteTierList,
|
||||
|
||||
@@ -31,6 +31,7 @@ const {
|
||||
listUsers,
|
||||
findPrimaryAdminUser,
|
||||
listAdminTierLists,
|
||||
summarizeAdminTierLists,
|
||||
findTierListById,
|
||||
listAdminTemplateRequests,
|
||||
findTemplateRequestById,
|
||||
@@ -310,6 +311,21 @@ router.get('/tierlists', requireAdmin, async (req, res) => {
|
||||
res.json(result)
|
||||
})
|
||||
|
||||
router.get('/tierlists/stats', requireAdmin, async (req, res) => {
|
||||
const schema = z.object({
|
||||
q: z.string().trim().max(120).optional().default(''),
|
||||
gameId: z.string().trim().max(120).optional().default(''),
|
||||
})
|
||||
const parsed = schema.safeParse(req.query)
|
||||
if (!parsed.success) return res.status(400).json({ error: 'bad_request' })
|
||||
|
||||
const result = await summarizeAdminTierLists({
|
||||
queryText: parsed.data.q,
|
||||
gameId: parsed.data.gameId,
|
||||
})
|
||||
res.json(result)
|
||||
})
|
||||
|
||||
router.get('/template-requests', requireAdmin, async (req, res) => {
|
||||
const requests = await listAdminTemplateRequests({ statuses: ['pending', 'reviewing'] })
|
||||
res.json({ requests })
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 의사결정 이력
|
||||
|
||||
## 2026-04-02 v1.3.69
|
||||
- 관리자 아이템 라이브러리의 참조 수는 저장 구조 설명에는 도움이 되지만 실제 운영에서는 대부분 의미가 약하므로, 카드와 모달에서 계속 전면에 두기보다 다시 숨기고 필요한 경우 내부 데이터로만 남기는 편이 맞다고 정리했다.
|
||||
- 관리자 상단 요약 수치는 `활성/대기` 같은 상태 문구보다 실제 운영 판단에 바로 쓰이는 `공개/비공개 개수`가 더 중요하므로, 게임 관리와 티어표 관리 모두 공개 상태 집계를 먼저 보여주는 편이 낫다고 판단했다.
|
||||
- 공개/비공개 수치는 현재 페이지 일부를 세면 오해가 생기기 쉬우므로, 검색/게임 기준 전체 결과를 집계하는 별도 관리자 API로 계산하는 편이 맞다고 정리했다.
|
||||
|
||||
## 2026-04-02 v1.3.68
|
||||
- 관리자 아이템 상세는 새 모달을 겹쳐 올리는 방식보다 기존 모달 안에서 `왼쪽 선택 대상 / 오른쪽 작업과 참조 정보` 역할만 분명히 나누는 편이 더 안정적이라고 정리했다.
|
||||
- 같은 이미지를 두 위치에서 반복 노출하면 “모달이 두 개 겹친 것처럼” 느껴질 수 있으므로, 선택 썸네일은 한 곳에만 두고 양쪽 패널은 각자 스크롤되는 구조로 정리하는 편이 맞다고 판단했다.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 할 일 및 이슈
|
||||
|
||||
## 단기 확인
|
||||
- 관리자 게임 관리 상단의 `전체 / 공개 / 비공개` 티어표 수치와 전체 티어표 관리 상단 집계가 실제 운영 데이터와 맞는지, 공개 전환 직후 숫자가 자연스럽게 갱신되는지 한 번 더 QA한다.
|
||||
- 관리자 아이템 상세 모달은 왼쪽 선택 카드와 오른쪽 상세 본문으로 다시 정리했으므로, 데스크톱/모바일에서 긴 게임 목록과 긴 참조 목록이 각각 자연스럽게 스크롤되는지 한 번 더 QA한다.
|
||||
- 관리자 계정으로 `/admin/...`을 직접 새로고침했을 때 홈으로 튕기지 않고 그대로 유지되는지, 실제 세션이 살아 있는 브라우저와 만료된 브라우저 각각에서 한 번 더 QA한다.
|
||||
- 티어표 만들기 화면의 보드 드롭존은 점선/높이/중앙 정렬로 존재감이 커졌으므로, 데스크톱과 모바일에서 파일 선택 버튼과 안내 문구 밀도를 한 번 더 QA한다.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 업데이트 로그
|
||||
|
||||
## 2026-04-02 v1.3.69
|
||||
- 관리자 아이템 라이브러리는 참조 수/공유 기록 UI가 실제 운영 판단에 비해 노이즈가 커 보여 카드 수치, 상세 모달의 같은 이미지 참조 섹션, 삭제 확인 문구의 참조 강조를 걷어내고 다시 항목 자체 관리 흐름 위주로 정리함.
|
||||
- 관리자 게임 관리 상단 요약은 더 이상 `선택 상태`처럼 추상적인 문구를 보여주지 않고, 선택된 게임 기준으로 만들어진 티어표의 `전체 / 공개 / 비공개` 개수를 바로 보여주도록 바꿈.
|
||||
- 전체 티어표 관리 상단에도 검색 결과 기준 `전체 / 공개 / 비공개` 수치를 함께 노출하고, 이를 위해 관리자 티어표 집계 API를 별도로 추가해 페이지 단위가 아니라 실제 전체 결과 기준 숫자를 안정적으로 표시함.
|
||||
|
||||
## 2026-04-02 v1.3.68
|
||||
- 관리자 아이템 상세 모달은 같은 이미지를 왼쪽 선택 카드와 오른쪽 본문에서 두 번 보여주던 중복 미리보기를 제거해, 한 모달 안에서 정보가 겹쳐 보이던 문제를 정리함.
|
||||
- 왼쪽 게임 선택 패널과 오른쪽 상세 정보 패널은 각각 독립 스크롤이 되도록 바꾸고, 스크롤바도 다시 보이게 조정해 긴 목록이나 긴 참조 정보가 있어도 레이아웃이 깨지지 않고 탐색할 수 있게 함.
|
||||
|
||||
@@ -19,10 +19,6 @@ const props = defineProps({
|
||||
<span class="customItemCard__badge" :class="{ 'customItemCard__badge--template': item.sourceType === 'template' }">{{ item.sourceLabel }}</span>
|
||||
<img class="customItemCard__image" :src="toApiUrl(item.src)" :alt="item.label" />
|
||||
<div class="customItemCard__title" :title="item.label">{{ item.label }}</div>
|
||||
<div class="customItemCard__stats">
|
||||
<span class="customItemCard__stat">참조 {{ item.sharedReferenceCount || 1 }}</span>
|
||||
<span class="customItemCard__stat">게임 {{ item.sharedLinkedGameCount || item.linkedGames?.length || 0 }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ const props = defineProps({
|
||||
adminTierListPage: { type: Number, required: true },
|
||||
adminTierListPageCount: { type: Number, required: true },
|
||||
adminTierListTotal: { type: Number, required: true },
|
||||
adminTierListStats: { type: Object, required: true },
|
||||
moveAdminTierListPage: { type: Function, required: true },
|
||||
})
|
||||
</script>
|
||||
@@ -128,6 +129,11 @@ const props = defineProps({
|
||||
<div class="sectionHeader">
|
||||
<div>
|
||||
<div class="panel__title">전체 티어표 관리</div>
|
||||
<div class="tierAdminHeaderStats">
|
||||
<span class="pill">전체 {{ props.adminTierListStats.total || 0 }}개</span>
|
||||
<span class="pill pill--soft">공개 {{ props.adminTierListStats.publicCount || 0 }}개</span>
|
||||
<span class="pill pill--soft">비공개 {{ props.adminTierListStats.privateCount || 0 }}개</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ export const api = {
|
||||
),
|
||||
listAdminTierLists: ({ q = '', page = 1, limit = 50 } = {}) =>
|
||||
request(`/api/admin/tierlists?q=${encodeURIComponent(q)}&page=${encodeURIComponent(page)}&limit=${encodeURIComponent(limit)}`),
|
||||
getAdminTierListStats: ({ q = '', gameId = '' } = {}) =>
|
||||
request(`/api/admin/tierlists/stats?q=${encodeURIComponent(q)}&gameId=${encodeURIComponent(gameId)}`),
|
||||
listAdminTemplateRequests: () => request('/api/admin/template-requests'),
|
||||
getAdminImageAssetStats: ({ month = '', limit = 12 } = {}) => {
|
||||
const query = new URLSearchParams()
|
||||
|
||||
@@ -52,6 +52,8 @@ const adminTierListQuery = ref('')
|
||||
const adminTierListPage = ref(1)
|
||||
const adminTierListLimit = ref(50)
|
||||
const adminTierListTotal = ref(0)
|
||||
const adminTierListStats = ref({ total: 0, publicCount: 0, privateCount: 0 })
|
||||
const selectedGameTierListStats = ref({ total: 0, publicCount: 0, privateCount: 0 })
|
||||
const templateRequests = ref([])
|
||||
const importModalOpen = ref(false)
|
||||
const importModalMode = ref('existing')
|
||||
@@ -228,7 +230,6 @@ const activeTabDescription = computed(() => {
|
||||
return '계정 정보, 권한, 비밀번호와 최근 활동을 더 가볍게 확인하고 수정합니다.'
|
||||
})
|
||||
const adminOverviewStats = computed(() => {
|
||||
const publishedTierLists = adminTierLists.value.filter((tierList) => tierList.isPublic).length
|
||||
const pendingRequests = templateRequests.value.length
|
||||
const orphanItems = customItems.value.filter((item) => item.usageCount === 0).length
|
||||
const adminCount = users.value.filter((user) => user.isAdmin).length
|
||||
@@ -243,7 +244,9 @@ const adminOverviewStats = computed(() => {
|
||||
if (activeTab.value === 'game-admin') {
|
||||
return [
|
||||
{ label: '전체 게임', value: `${games.value.length}` },
|
||||
{ label: '선택 상태', value: hasSelectedGame.value ? '활성' : '대기' },
|
||||
{ label: '티어표 전체', value: `${selectedGameTierListStats.value.total || 0}` },
|
||||
{ label: '공개', value: `${selectedGameTierListStats.value.publicCount || 0}` },
|
||||
{ label: '비공개', value: `${selectedGameTierListStats.value.privateCount || 0}` },
|
||||
{ label: '기본 아이템', value: `${selectedGame.value?.items?.length || 0}` },
|
||||
]
|
||||
}
|
||||
@@ -263,8 +266,9 @@ const adminOverviewStats = computed(() => {
|
||||
{ label: '업데이트 요청', value: `${templateRequests.value.filter((request) => request.type === 'update').length}` },
|
||||
]
|
||||
: [
|
||||
{ label: '검색 결과', value: `${adminTierListTotal.value}` },
|
||||
{ label: '공개 티어표', value: `${publishedTierLists}` },
|
||||
{ label: '검색 결과', value: `${adminTierListStats.value.total || 0}` },
|
||||
{ label: '공개', value: `${adminTierListStats.value.publicCount || 0}` },
|
||||
{ label: '비공개', value: `${adminTierListStats.value.privateCount || 0}` },
|
||||
{ label: '현재 페이지', value: `${adminTierListPage.value}/${adminTierListPageCount.value}` },
|
||||
]
|
||||
}
|
||||
@@ -431,6 +435,14 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => selectedGame.value?.game?.id || '',
|
||||
async (gameId) => {
|
||||
await refreshSelectedGameTierListStats(gameId)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
() => tierlistsMode.value,
|
||||
(mode) => {
|
||||
@@ -559,17 +571,13 @@ function formatImageJobStatus(status) {
|
||||
|
||||
function customItemDeleteImpactText(item) {
|
||||
if (!item) return ''
|
||||
const sharedCount = Number(item.sharedReferenceCount || 1)
|
||||
|
||||
if (item.sourceType === 'template') {
|
||||
const base = item.isAssetLibraryItem
|
||||
return item.isAssetLibraryItem
|
||||
? `"${item.label}" 보관 자산 항목을 정리할까요? 라이브러리 항목만 제거되고, 같은 이미지를 쓰는 다른 참조는 그대로 유지됩니다.`
|
||||
: `"${item.label}" 템플릿 항목을 정리할까요? 연결된 템플릿과 같은 게임의 저장된 티어표에서 이 항목이 함께 제거될 수 있어요.`
|
||||
return sharedCount > 1 ? `${base} 현재 같은 이미지 참조 ${sharedCount}건 중 이 항목만 다룹니다.` : base
|
||||
}
|
||||
|
||||
const base = `"${item.label}" 사용자 업로드 이미지를 삭제할까요? 같은 이미지를 쓰는 다른 참조는 그대로 유지됩니다.`
|
||||
return sharedCount > 1 ? `${base} 현재 같은 이미지 참조 ${sharedCount}건 중 이 항목만 다룹니다.` : base
|
||||
return `"${item.label}" 사용자 업로드 이미지를 삭제할까요? 현재 항목만 정리됩니다.`
|
||||
}
|
||||
|
||||
const imageDiagnosticsCards = computed(() => {
|
||||
@@ -587,7 +595,6 @@ const imageDiagnosticsCards = computed(() => {
|
||||
const visibleLinkedGames = computed(() =>
|
||||
(modalTargetCustomItem.value?.linkedGames || []).filter((game) => game?.id && game.id !== 'freeform')
|
||||
)
|
||||
const visibleSharedEntries = computed(() => modalTargetCustomItem.value?.sharedEntries || [])
|
||||
const filteredCustomItemModalGames = computed(() => {
|
||||
const query = customItemModalGameQuery.value.trim().toLowerCase()
|
||||
const linkedIds = new Set(visibleLinkedGames.value.map((game) => game.id))
|
||||
@@ -810,11 +817,44 @@ async function refreshAdminTierLists() {
|
||||
adminTierListTotal.value = data.total || 0
|
||||
adminTierListPage.value = data.page || 1
|
||||
adminTierListLimit.value = data.limit || adminTierListLimit.value
|
||||
await refreshAdminTierListStats()
|
||||
} catch (e) {
|
||||
error.value = '관리자 티어표 목록을 불러오지 못했어요.'
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAdminTierListStats() {
|
||||
if (!auth.user?.isAdmin) return
|
||||
try {
|
||||
const data = await api.getAdminTierListStats({ q: adminTierListQuery.value })
|
||||
adminTierListStats.value = {
|
||||
total: data.total || 0,
|
||||
publicCount: data.publicCount || 0,
|
||||
privateCount: data.privateCount || 0,
|
||||
}
|
||||
} catch (e) {
|
||||
adminTierListStats.value = { total: 0, publicCount: 0, privateCount: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshSelectedGameTierListStats(gameId = '') {
|
||||
if (!auth.user?.isAdmin || !gameId) {
|
||||
selectedGameTierListStats.value = { total: 0, publicCount: 0, privateCount: 0 }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await api.getAdminTierListStats({ gameId })
|
||||
selectedGameTierListStats.value = {
|
||||
total: data.total || 0,
|
||||
publicCount: data.publicCount || 0,
|
||||
privateCount: data.privateCount || 0,
|
||||
}
|
||||
} catch (e) {
|
||||
selectedGameTierListStats.value = { total: 0, publicCount: 0, privateCount: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshTemplateRequests() {
|
||||
if (!auth.user?.isAdmin) return
|
||||
try {
|
||||
@@ -1261,12 +1301,6 @@ function buildModalItemFromTierListItem(item, tierList) {
|
||||
sourceLabel: matchedItem?.sourceLabel || '티어표 추가 아이템',
|
||||
ownerName: matchedItem?.ownerName || tierListAuthorDisplayName(tierList),
|
||||
linkedGames: Array.isArray(matchedItem?.linkedGames) ? matchedItem.linkedGames : [],
|
||||
sharedReferenceCount: matchedItem?.sharedReferenceCount || 1,
|
||||
sharedUserReferenceCount: matchedItem?.sharedUserReferenceCount || 0,
|
||||
sharedTemplateReferenceCount: matchedItem?.sharedTemplateReferenceCount || 0,
|
||||
sharedAssetReferenceCount: matchedItem?.sharedAssetReferenceCount || 0,
|
||||
sharedLinkedGameCount: matchedItem?.sharedLinkedGameCount || 0,
|
||||
sharedEntries: Array.isArray(matchedItem?.sharedEntries) ? matchedItem.sharedEntries : [],
|
||||
usageCount: matchedItem?.usageCount || 0,
|
||||
canDelete: typeof matchedItem?.canDelete === 'boolean' ? matchedItem.canDelete : false,
|
||||
isPromoting: false,
|
||||
@@ -1599,6 +1633,7 @@ function userAvatarFallback(user) {
|
||||
:admin-tier-list-page="adminTierListPage"
|
||||
:admin-tier-list-page-count="adminTierListPageCount"
|
||||
:admin-tier-list-total="adminTierListTotal"
|
||||
:admin-tier-list-stats="adminTierListStats"
|
||||
:move-admin-tier-list-page="moveAdminTierListPage"
|
||||
/>
|
||||
|
||||
@@ -1796,10 +1831,8 @@ function userAvatarFallback(user) {
|
||||
<div class="customItemModal__selectedMeta">
|
||||
<div class="customItemModal__selectedTitle">{{ modalTargetCustomItem.label }}</div>
|
||||
<div class="customItemModal__selectedChips">
|
||||
<span class="pill">{{ modalTargetCustomItem.sharedReferenceCount || 1 }}개 참조</span>
|
||||
<span class="pill" v-if="modalTargetCustomItem.sharedUserReferenceCount">사용자 {{ modalTargetCustomItem.sharedUserReferenceCount }}</span>
|
||||
<span class="pill" v-if="modalTargetCustomItem.sharedTemplateReferenceCount">템플릿 {{ modalTargetCustomItem.sharedTemplateReferenceCount }}</span>
|
||||
<span class="pill" v-if="modalTargetCustomItem.sharedAssetReferenceCount">보관 {{ modalTargetCustomItem.sharedAssetReferenceCount }}</span>
|
||||
<span class="pill">{{ modalTargetCustomItem.sourceLabel }}</span>
|
||||
<span class="pill" v-if="modalTargetCustomItem.ownerName">{{ modalTargetCustomItem.ownerName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1863,25 +1896,6 @@ function userAvatarFallback(user) {
|
||||
<button v-for="game in visibleLinkedGames" :key="game.id" type="button" class="pill pill--link" @click="jumpToGameAdmin(game.id)">{{ game.name }}</button>
|
||||
</div>
|
||||
<div v-else class="hint hint--tight">아직 템플릿에 연결된 게임이 없어요.</div>
|
||||
</div>
|
||||
<div class="customItemModal__linked">
|
||||
<span class="customItemModal__label">같은 이미지 참조</span>
|
||||
<div class="customItemModal__metaList">
|
||||
<div class="customItemModal__metaRow"><span>총 참조</span><strong>{{ modalTargetCustomItem.sharedReferenceCount || 1 }}건</strong></div>
|
||||
<div class="customItemModal__metaRow"><span>사용자 업로드</span><strong>{{ modalTargetCustomItem.sharedUserReferenceCount || 0 }}건</strong></div>
|
||||
<div class="customItemModal__metaRow"><span>템플릿 항목</span><strong>{{ modalTargetCustomItem.sharedTemplateReferenceCount || 0 }}건</strong></div>
|
||||
<div class="customItemModal__metaRow"><span>보관 자산</span><strong>{{ modalTargetCustomItem.sharedAssetReferenceCount || 0 }}건</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customItemModal__linked">
|
||||
<span class="customItemModal__label">같은 이미지 기록</span>
|
||||
<div v-if="visibleSharedEntries.length" class="customItemModal__entryList">
|
||||
<div v-for="entry in visibleSharedEntries" :key="entry.id" class="customItemModal__entry">
|
||||
<div class="customItemModal__entryTitle">{{ entry.label }}</div>
|
||||
<div class="customItemModal__entryMeta">{{ entry.sourceLabel }} · {{ entry.ownerName || '알 수 없음' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="hint hint--tight">같은 이미지를 가리키는 다른 기록이 없어요.</div>
|
||||
</div>
|
||||
<div class="customItemModal__actions">
|
||||
<a class="btn btn--ghost customItemModal__action" :href="toApiUrl(modalTargetCustomItem.src)" :download="modalTargetCustomItem.label">이미지 다운로드</a>
|
||||
@@ -3195,15 +3209,6 @@ function userAvatarFallback(user) {
|
||||
line-height: 1.3;
|
||||
color: var(--theme-text);
|
||||
}
|
||||
.adminUiScope .customItemCard__stats {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.adminUiScope .customItemCard__stat {
|
||||
font-size: 11px;
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
.adminUiScope .customItemModal {
|
||||
display: grid;
|
||||
grid-template-columns: 340px minmax(0, 1fr);
|
||||
@@ -3359,25 +3364,6 @@ function userAvatarFallback(user) {
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft);
|
||||
}
|
||||
.adminUiScope .customItemModal__entryList {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.adminUiScope .customItemModal__entry {
|
||||
padding: 10px 12px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-surface-soft);
|
||||
}
|
||||
.adminUiScope .customItemModal__entryTitle {
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.adminUiScope .customItemModal__entryMeta {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
color: var(--theme-text-soft);
|
||||
}
|
||||
.adminUiScope .customItemModal__close {
|
||||
justify-self: end;
|
||||
border: 0;
|
||||
@@ -4010,6 +3996,12 @@ function userAvatarFallback(user) {
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.adminUiScope .tierAdminHeaderStats {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.adminUiScope .pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user