Huge bunch of UI/UX improvements and tweaks!
This commit is contained in:
parent
501be10bfe
commit
fb25e72215
37 changed files with 1651 additions and 949 deletions
|
|
@ -1,19 +1,23 @@
|
|||
<template>
|
||||
<div class="admin-member-detail">
|
||||
<!-- Page Header -->
|
||||
<div class="page-header">
|
||||
<div class="header-nav">
|
||||
<NuxtLink to="/admin/members" class="back-link">← Members</NuxtLink>
|
||||
<NuxtLink v-if="member && member.status === 'active' && member.showInDirectory" :to="`/members/${member._id}`" class="profile-link" target="_blank">
|
||||
View public profile ↗
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<div class="header-row">
|
||||
<div>
|
||||
<NuxtLink to="/admin/members" class="back-link">← Members</NuxtLink>
|
||||
<h1 v-if="member">{{ member.name }}</h1>
|
||||
<h1 v-else-if="pending">Loading…</h1>
|
||||
<h1 v-else>Member not found</h1>
|
||||
<p v-if="member" class="member-email">{{ member.email }}</p>
|
||||
</div>
|
||||
<div v-if="member" class="header-actions">
|
||||
<div v-if="member" class="header-badges">
|
||||
<span class="badge" :class="member.circle">{{ member.circle }}</span>
|
||||
<span :class="statusClass(member.status)" class="status-badge">{{
|
||||
member.status
|
||||
}}</span>
|
||||
<span :class="statusClass(member.status)" class="status-badge">{{ member.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -26,165 +30,173 @@
|
|||
<div v-else-if="fetchError" class="error-state">Failed to load member.</div>
|
||||
|
||||
<template v-else-if="member">
|
||||
<!-- Edit form -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Member details</div>
|
||||
<form class="edit-form" @submit.prevent="submitEdit">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input v-model="form.name" type="text" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input v-model="form.email" type="email" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Circle</label>
|
||||
<select v-model="form.circle">
|
||||
<option value="community">Community</option>
|
||||
<option value="founder">Founder</option>
|
||||
<option value="practitioner">Practitioner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Contribution tier ($/mo)</label>
|
||||
<select v-model="form.contributionTier">
|
||||
<option value="0">$0</option>
|
||||
<option value="5">$5</option>
|
||||
<option value="15">$15</option>
|
||||
<option value="30">$30</option>
|
||||
<option value="50">$50</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select v-model="form.status">
|
||||
<option value="pending_payment">pending_payment</option>
|
||||
<option value="active">active</option>
|
||||
<option value="suspended">suspended</option>
|
||||
<option value="cancelled">cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Role</label>
|
||||
<select v-model="form.role">
|
||||
<option value="member">member</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? "Saving…" : "Save changes" }}
|
||||
</button>
|
||||
<button type="button" class="btn" @click="resetForm">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<div class="detail-body">
|
||||
<!-- LEFT COLUMN: form + metadata -->
|
||||
<div class="detail-left">
|
||||
<!-- Edit form -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Member details</div>
|
||||
<form class="edit-form" @submit.prevent="submitEdit">
|
||||
<div class="field">
|
||||
<label>Name</label>
|
||||
<input v-model="form.name" type="text" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Email</label>
|
||||
<input v-model="form.email" type="email" required />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Circle</label>
|
||||
<select v-model="form.circle">
|
||||
<option value="community">Community</option>
|
||||
<option value="founder">Founder</option>
|
||||
<option value="practitioner">Practitioner</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Contribution tier ($/mo)</label>
|
||||
<select v-model="form.contributionTier">
|
||||
<option value="0">$0</option>
|
||||
<option value="5">$5</option>
|
||||
<option value="15">$15</option>
|
||||
<option value="30">$30</option>
|
||||
<option value="50">$50</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Status</label>
|
||||
<select v-model="form.status">
|
||||
<option value="pending_payment">pending_payment</option>
|
||||
<option value="active">active</option>
|
||||
<option value="suspended">suspended</option>
|
||||
<option value="cancelled">cancelled</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Role</label>
|
||||
<select v-model="form.role">
|
||||
<option value="member">member</option>
|
||||
<option value="admin">admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" :disabled="saving">
|
||||
{{ saving ? "Saving…" : "Save changes" }}
|
||||
</button>
|
||||
<button type="button" class="btn" @click="resetForm">Reset</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<!-- Metadata -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Account info</div>
|
||||
<dl class="meta-list">
|
||||
<div class="meta-row">
|
||||
<dt>Member ID</dt>
|
||||
<dd class="mono">{{ member._id }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Joined</dt>
|
||||
<dd>{{ formatDate(member.createdAt) }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Invite email</dt>
|
||||
<dd :class="member.inviteEmailSent ? 'status-ok' : 'status-dim'">
|
||||
{{ member.inviteEmailSent ? "Sent" : "Not sent" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Slack invite</dt>
|
||||
<dd :class="member.slackInvited ? 'status-ok' : 'status-dim'">
|
||||
{{ member.slackInvited ? "Invited" : "Pending" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="member.helcimCustomerId" class="meta-row">
|
||||
<dt>Helcim customer</dt>
|
||||
<dd class="mono">{{ member.helcimCustomerId }}</dd>
|
||||
</div>
|
||||
<div v-if="member.helcimSubscriptionId" class="meta-row">
|
||||
<dt>Helcim subscription</dt>
|
||||
<dd class="mono">{{ member.helcimSubscriptionId }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
<!-- Metadata -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Account info</div>
|
||||
<dl class="meta-list">
|
||||
<div v-if="member.memberNumber" class="meta-row">
|
||||
<dt>Member number</dt>
|
||||
<dd class="mono">#{{ member.memberNumber }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Member ID</dt>
|
||||
<dd class="mono">{{ member._id }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Joined</dt>
|
||||
<dd>{{ formatDate(member.createdAt) }}</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Invite email</dt>
|
||||
<dd :class="member.inviteEmailSent ? 'status-ok' : 'status-dim'">
|
||||
{{ member.inviteEmailSent ? "Sent" : "Not sent" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Slack invite</dt>
|
||||
<dd :class="member.slackInvited ? 'status-ok' : 'status-dim'">
|
||||
{{ member.slackInvited ? "Invited" : "Pending" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div v-if="member.helcimCustomerId" class="meta-row">
|
||||
<dt>Helcim customer</dt>
|
||||
<dd class="mono">{{ member.helcimCustomerId }}</dd>
|
||||
</div>
|
||||
<div v-if="member.helcimSubscriptionId" class="meta-row">
|
||||
<dt>Helcim subscription</dt>
|
||||
<dd class="mono">{{ member.helcimSubscriptionId }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<!-- Notification preferences -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Notification preferences</div>
|
||||
<dl class="meta-list">
|
||||
<div class="meta-row">
|
||||
<dt>Event reminders</dt>
|
||||
<dd
|
||||
:class="
|
||||
member.notifications?.events !== false
|
||||
? 'status-ok'
|
||||
: 'status-dim'
|
||||
"
|
||||
>
|
||||
{{ member.notifications?.events !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Community updates</dt>
|
||||
<dd
|
||||
:class="
|
||||
member.notifications?.updates !== false
|
||||
? 'status-ok'
|
||||
: 'status-dim'
|
||||
"
|
||||
>
|
||||
{{ member.notifications?.updates !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Peer support requests</dt>
|
||||
<dd
|
||||
:class="
|
||||
member.notifications?.peerRequests !== false
|
||||
? 'status-ok'
|
||||
: 'status-dim'
|
||||
"
|
||||
>
|
||||
{{ member.notifications?.peerRequests !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
<!-- Notification preferences -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Notification preferences</div>
|
||||
<dl class="meta-list">
|
||||
<div class="meta-row">
|
||||
<dt>Event reminders</dt>
|
||||
<dd :class="member.notifications?.events !== false ? 'status-ok' : 'status-dim'">
|
||||
{{ member.notifications?.events !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Community updates</dt>
|
||||
<dd :class="member.notifications?.updates !== false ? 'status-ok' : 'status-dim'">
|
||||
{{ member.notifications?.updates !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<dt>Peer support requests</dt>
|
||||
<dd :class="member.notifications?.peerRequests !== false ? 'status-ok' : 'status-dim'">
|
||||
{{ member.notifications?.peerRequests !== false ? "On" : "Off" }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Full Activity Log -->
|
||||
<section class="detail-section">
|
||||
<div class="section-label">Full Activity Log</div>
|
||||
<ClientOnly>
|
||||
<div v-if="activityLoading && !activityEntries.length" class="loading-state">
|
||||
<div class="spinner" />
|
||||
Loading activity...
|
||||
</div>
|
||||
<div v-else-if="activityEntries.length" class="activity-log">
|
||||
<div v-for="entry in activityEntries" :key="entry._id" class="al-item" :class="{ 'al-admin': entry.visibility === 'admin' }">
|
||||
<UIcon :name="getActivity(entry).icon" class="al-icon" />
|
||||
<span class="al-text">{{ getActivity(entry).text }}</span>
|
||||
<span class="al-time">{{ formatDate(entry.timestamp) }}</span>
|
||||
<span v-if="entry.visibility === 'admin'" class="al-vis-badge">admin-only</span>
|
||||
</div>
|
||||
<div v-if="activityHasMore" class="al-load-more">
|
||||
<button class="btn" :disabled="activityLoadingMore" @click="loadMoreActivity">
|
||||
{{ activityLoadingMore ? 'Loading...' : 'Load More' }}
|
||||
</button>
|
||||
<!-- RIGHT COLUMN: activity log -->
|
||||
<div class="detail-right">
|
||||
<div class="activity-panel">
|
||||
<div class="activity-panel-header">
|
||||
<div class="section-label">Activity log</div>
|
||||
<span class="activity-legend">
|
||||
<span class="al-vis-badge">admin-only</span> = not visible to member
|
||||
</span>
|
||||
</div>
|
||||
<ClientOnly>
|
||||
<div v-if="activityLoading && !activityEntries.length" class="activity-loading">
|
||||
<div class="spinner" />
|
||||
Loading activity...
|
||||
</div>
|
||||
<div v-else-if="activityEntries.length" class="activity-timeline">
|
||||
<div
|
||||
v-for="entry in activityEntries"
|
||||
:key="entry._id"
|
||||
class="al-item"
|
||||
:class="{ 'al-admin': entry.visibility === 'admin' }"
|
||||
>
|
||||
<div class="al-dot" />
|
||||
<div class="al-body">
|
||||
<div class="al-row">
|
||||
<UIcon :name="getActivity(entry).icon" class="al-icon" />
|
||||
<span class="al-text">{{ getActivity(entry).text }}</span>
|
||||
<span v-if="entry.visibility === 'admin'" class="al-vis-badge">admin-only</span>
|
||||
</div>
|
||||
<span class="al-time">{{ formatDate(entry.timestamp) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activityHasMore" class="al-load-more">
|
||||
<button class="btn" :disabled="activityLoadingMore" @click="loadMoreActivity">
|
||||
{{ activityLoadingMore ? 'Loading...' : 'Load more' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="activity-empty">
|
||||
No activity recorded.
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<div v-else class="loading-state">
|
||||
No activity recorded.
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -271,12 +283,12 @@ async function submitEdit() {
|
|||
member.value = { ...member.value, ...updated, role: form.role };
|
||||
pageBreadcrumbTitle.value = form.name;
|
||||
}
|
||||
toast.add({ title: "Member updated", color: "green" });
|
||||
toast.add({ title: "Member updated", color: "success" });
|
||||
} catch (err) {
|
||||
toast.add({
|
||||
title: "Failed to update member",
|
||||
description: err.data?.statusMessage || err.message,
|
||||
color: "red",
|
||||
color: "error",
|
||||
});
|
||||
} finally {
|
||||
saving.value = false;
|
||||
|
|
@ -344,13 +356,42 @@ onMounted(loadActivity)
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-member-detail {
|
||||
max-width: 640px;
|
||||
padding: 32px 40px 60px;
|
||||
.admin-member-detail {}
|
||||
|
||||
/* ---- PAGE HEADER ---- */
|
||||
.page-header {
|
||||
padding: 28px 28px 20px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 32px;
|
||||
.header-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--candle);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.profile-link {
|
||||
font-size: 11px;
|
||||
color: var(--candle);
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.profile-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
|
|
@ -360,26 +401,13 @@ onMounted(loadActivity)
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
text-decoration: none;
|
||||
margin-bottom: 8px;
|
||||
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
color: var(--candle);
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-family: "Brygada 1918", serif;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
font-weight: 500;
|
||||
color: var(--text-bright);
|
||||
margin: 0 0 4px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.member-email {
|
||||
|
|
@ -388,29 +416,44 @@ onMounted(loadActivity)
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
.header-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
padding-top: 6px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-family: "Commit Mono", monospace;
|
||||
padding: 2px 8px;
|
||||
border: 1px dashed var(--border);
|
||||
color: var(--text-dim);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* ---- TWO-COLUMN BODY ---- */
|
||||
.detail-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.detail-left {
|
||||
border-right: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin-bottom: 40px;
|
||||
padding: 24px 28px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
|
|
@ -418,6 +461,8 @@ onMounted(loadActivity)
|
|||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.meta-list {
|
||||
|
|
@ -430,8 +475,8 @@ onMounted(loadActivity)
|
|||
|
||||
.meta-row {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
padding: 10px 14px;
|
||||
gap: 16px;
|
||||
padding: 9px 14px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
|
|
@ -452,6 +497,7 @@ onMounted(loadActivity)
|
|||
font-size: 12px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.mono {
|
||||
|
|
@ -459,8 +505,9 @@ onMounted(loadActivity)
|
|||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* ---- STATUS ---- */
|
||||
.status-ok {
|
||||
color: var(--c-founder);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.status-dim {
|
||||
|
|
@ -471,6 +518,7 @@ onMounted(loadActivity)
|
|||
color: var(--ember);
|
||||
}
|
||||
|
||||
/* ---- STATES ---- */
|
||||
.loading-state,
|
||||
.error-state {
|
||||
display: flex;
|
||||
|
|
@ -478,46 +526,122 @@ onMounted(loadActivity)
|
|||
gap: 12px;
|
||||
color: var(--text-dim);
|
||||
font-size: 13px;
|
||||
padding: 40px 0;
|
||||
padding: 40px 28px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--candle);
|
||||
border: 2px dashed var(--candle);
|
||||
border-top-color: transparent;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.7s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ---- ACTIVITY LOG ---- */
|
||||
.activity-log {
|
||||
margin-top: 12px;
|
||||
border: 1px dashed var(--border);
|
||||
/* ---- ACTIVITY PANEL ---- */
|
||||
.detail-right {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.al-item {
|
||||
.activity-panel {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 120px);
|
||||
}
|
||||
|
||||
.activity-panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
justify-content: space-between;
|
||||
padding: 24px 28px 16px;
|
||||
border-bottom: 1px dashed var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.activity-legend {
|
||||
font-size: 10px;
|
||||
color: var(--text-faint);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.activity-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 32px 28px;
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.al-item:last-child {
|
||||
border-bottom: none;
|
||||
.activity-empty {
|
||||
padding: 32px 28px;
|
||||
color: var(--text-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.al-admin {
|
||||
opacity: 0.7;
|
||||
/* Timeline */
|
||||
.activity-timeline {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding: 16px 0 24px;
|
||||
}
|
||||
|
||||
.al-item {
|
||||
display: grid;
|
||||
grid-template-columns: 20px 1fr;
|
||||
gap: 0 10px;
|
||||
padding: 0 28px 0 20px;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.al-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 27px;
|
||||
top: 18px;
|
||||
bottom: -16px;
|
||||
width: 1px;
|
||||
border-left: 1px dashed var(--border);
|
||||
}
|
||||
|
||||
.al-item:last-child::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.al-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border: 1px dashed var(--border);
|
||||
background: var(--bg);
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
.al-admin .al-dot {
|
||||
border-color: var(--candle-faint);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.al-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.al-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.al-icon {
|
||||
|
|
@ -525,41 +649,75 @@ onMounted(loadActivity)
|
|||
height: 14px;
|
||||
color: var(--text-faint);
|
||||
flex-shrink: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.al-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--text-dim);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.al-time {
|
||||
display: block;
|
||||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
font-size: 10px;
|
||||
margin-top: 3px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.al-vis-badge {
|
||||
font-size: 9px;
|
||||
color: var(--text-faint);
|
||||
border: 1px dashed var(--border);
|
||||
padding: 1px 4px;
|
||||
color: var(--candle);
|
||||
border: 1px dashed var(--candle-faint);
|
||||
padding: 1px 5px;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.al-load-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
padding: 8px 28px 0;
|
||||
}
|
||||
|
||||
/* ---- RESPONSIVE ---- */
|
||||
@media (max-width: 1024px) {
|
||||
.detail-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.detail-left {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.activity-panel {
|
||||
position: static;
|
||||
max-height: none;
|
||||
border-top: 1px dashed var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-member-detail {
|
||||
padding: 20px 16px 40px;
|
||||
.page-header {
|
||||
padding: 24px 20px 16px;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
.detail-section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.activity-panel-header {
|
||||
padding: 16px 20px 12px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.al-item {
|
||||
padding: 0 20px 0 14px;
|
||||
}
|
||||
|
||||
.meta-row {
|
||||
|
|
|
|||
1281
app/pages/admin/members/index.vue
Normal file
1281
app/pages/admin/members/index.vue
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue