feat(sidebar): show user info with three-state UI (guest/member/vip)
Sidebar screen now primarily displays user-related information and derives three render states from loginStatus + currentUser.isVip: - guest (notLoggedIn): welcome panel + Sign in / Sign up entries - member (logged in, no VIP): user info card + Get VIP CTA + Log out - vip (logged in, VIP): user info card with VIP badge + Manage VIP CTA + Log out Adds UserView.isVip (default false), patches user-machine.toView() to populate it, and introduces three presentational components (GuestPanel, UserInfoCard, VipCta) co-located under src/app/sidebar. Preserves existing UserInit + logout-redirect behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -47,3 +47,229 @@
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-lg, 16px);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 用户信息区
|
||||
* ============================================================ */
|
||||
.profileArea {
|
||||
padding: 0 var(--spacing-md, 12px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-lg, 16px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.userInfoCard {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md, 12px);
|
||||
padding: var(--spacing-lg, 16px);
|
||||
background: var(--color-settings-card-background, #ffffff);
|
||||
border-radius: var(--radius-lg, 12px);
|
||||
color: #000;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.userInfoAvatar {
|
||||
border-radius: var(--radius-full, 999px);
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.userInfoAvatarFallback {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: var(--radius-full, 999px);
|
||||
background: var(--color-settings-row-background, #f2f2f2);
|
||||
color: #000;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xl, 20px);
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.userInfoText {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs, 4px);
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.userInfoNameRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.userInfoName {
|
||||
font-size: var(--font-size-lg, 16px);
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.userInfoEmail {
|
||||
font-size: var(--font-size-sm, 14px);
|
||||
color: var(--color-text-secondary, #666);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.userInfoCoinRow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs, 4px);
|
||||
font-size: var(--font-size-sm, 14px);
|
||||
color: var(--color-text-secondary, #666);
|
||||
}
|
||||
|
||||
.vipBadge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px var(--spacing-sm, 8px);
|
||||
border-radius: var(--radius-full, 999px);
|
||||
background: var(--color-accent, #7c5cff);
|
||||
color: var(--color-text-primary, #ffffff);
|
||||
font-size: var(--font-size-xs, 12px);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vipCta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-md, 12px) var(--spacing-lg, 16px);
|
||||
border-radius: var(--radius-full, 999px);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-button-gradient-start, #7c5cff),
|
||||
var(--color-button-gradient-end, #b48bff)
|
||||
);
|
||||
color: var(--color-text-primary, #ffffff);
|
||||
font-size: var(--font-size-lg, 16px);
|
||||
font-weight: 600;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.vipCta:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.vipCta:focus-visible {
|
||||
outline: 2px solid var(--color-accent, #7c5cff);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 未登录态 GuestPanel
|
||||
* ============================================================ */
|
||||
.guestPanel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md, 12px);
|
||||
padding: var(--spacing-lg, 16px);
|
||||
background: var(--color-settings-card-background, #ffffff);
|
||||
border-radius: var(--radius-lg, 12px);
|
||||
color: #000;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.guestTitle {
|
||||
font-size: var(--font-size-xl, 20px);
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.guestSubtitle {
|
||||
font-size: var(--font-size-sm, 14px);
|
||||
color: var(--color-text-secondary, #666);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.guestActions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm, 8px);
|
||||
margin-top: var(--spacing-sm, 8px);
|
||||
}
|
||||
|
||||
.guestPrimaryLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-md, 12px) var(--spacing-lg, 16px);
|
||||
border-radius: var(--radius-full, 999px);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-button-gradient-start, #7c5cff),
|
||||
var(--color-button-gradient-end, #b48bff)
|
||||
);
|
||||
color: var(--color-text-primary, #ffffff);
|
||||
font-size: var(--font-size-lg, 16px);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.guestPrimaryLink:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.guestSecondaryLink {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-md, 12px) var(--spacing-lg, 16px);
|
||||
border-radius: var(--radius-full, 999px);
|
||||
background: transparent;
|
||||
color: #000;
|
||||
font-size: var(--font-size-lg, 16px);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--color-auth-border, #d0d0d0);
|
||||
}
|
||||
|
||||
.guestSecondaryLink:hover {
|
||||
background: var(--color-settings-row-background, #f2f2f2);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 骨架占位
|
||||
* ============================================================ */
|
||||
.skeleton {
|
||||
display: block;
|
||||
background: var(--color-settings-row-background, #e5e5e5);
|
||||
border-radius: var(--radius-md, 8px);
|
||||
}
|
||||
|
||||
.skeletonMd {
|
||||
width: 60%;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.skeletonSm {
|
||||
width: 80%;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.skeletonXs {
|
||||
width: 40%;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user