mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
Display new badges on new cards
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@@ -4,7 +4,7 @@ import { IconList, IconPaperclip } from '@tabler/icons-react';
|
||||
|
||||
import { AttachmentCard } from '~/components/Article/Detail/AttachmentCard';
|
||||
import { TableOfContent } from '~/components/Article/Detail/TableOfContent';
|
||||
import { CreatorCardV2 } from '~/components/CreatorCard/CreatorCard';
|
||||
import { SmartCreatorCard } from '~/components/CreatorCard/CreatorCard';
|
||||
import { useHeadingsData } from '~/hooks/useHeadingsData';
|
||||
import { hideMobile } from '~/libs/sx-helpers';
|
||||
import type { ArticleGetById } from '~/server/services/article.service';
|
||||
@@ -78,7 +78,7 @@ export function Sidebar({ articleId, attachments, creator }: Props) {
|
||||
</Stack>
|
||||
</Accordion>
|
||||
)}
|
||||
<CreatorCardV2 user={creator} tipBuzzEntityId={articleId} tipBuzzEntityType="Article" />
|
||||
<SmartCreatorCard user={creator} tipBuzzEntityId={articleId} tipBuzzEntityType="Article" />
|
||||
</Stack>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { showErrorNotification, showSuccessNotification } from '~/utils/notifica
|
||||
import { BountyGetById } from '~/types/router';
|
||||
import { useCurrentUser } from '~/hooks/useCurrentUser';
|
||||
import { getBountyCurrency } from '~/components/Bounty/bounty.utils';
|
||||
import { CreatorCardV2 } from '~/components/CreatorCard/CreatorCard';
|
||||
import { SmartCreatorCard } from '~/components/CreatorCard/CreatorCard';
|
||||
import { formatDate } from '~/utils/date-helpers';
|
||||
import { useTrackEvent } from '../TrackView/track.utils';
|
||||
|
||||
@@ -172,7 +172,7 @@ export const AwardBountyAction = ({
|
||||
<Text size="xs" color="dimmed">
|
||||
Entry added on {formatDate(bountyEntry.createdAt)} by
|
||||
</Text>
|
||||
<CreatorCardV2 user={bountyEntry.user} />
|
||||
<SmartCreatorCard user={bountyEntry.user} />
|
||||
</Stack>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,7 @@ import { formatDate } from '~/utils/date-helpers';
|
||||
import { sortDomainLinks } from '~/utils/domain-link';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
import { TipBuzzButton } from '../Buzz/TipBuzzButton';
|
||||
import { UserStatBadges } from '../UserStatBadges/UserStatBadges';
|
||||
import { UserStatBadges, UserStatBadgesV2 } from '../UserStatBadges/UserStatBadges';
|
||||
import { getEdgeUrl } from '~/client-utils/cf-images-utils';
|
||||
import {
|
||||
BadgeCosmetic,
|
||||
@@ -35,6 +35,7 @@ import { isDefined } from '~/utils/type-guards';
|
||||
import { BadgeDisplay, Username } from '../User/Username';
|
||||
import { createServerSideProps } from '~/server/utils/server-side-helpers';
|
||||
import { UserPublicSettingsSchema } from '~/server/schema/user.schema';
|
||||
import { useFeatureFlags } from '~/providers/FeatureFlagsProvider';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
profileDetailsContainer: {
|
||||
@@ -66,6 +67,7 @@ export function CreatorCard({
|
||||
tipBuzzEntityType,
|
||||
tipBuzzEntityId,
|
||||
withActions = true,
|
||||
...cardProps
|
||||
}: Props) {
|
||||
const { data } = trpc.user.getCreator.useQuery(
|
||||
{ id: user.id },
|
||||
@@ -90,86 +92,46 @@ export function CreatorCard({
|
||||
|
||||
if (!creator || user.id === -1) return null;
|
||||
|
||||
const backgroundImage = creator.cosmetics.find(({ cosmetic }) =>
|
||||
cosmetic ? cosmetic.type === 'ProfileBackground' : undefined
|
||||
)?.cosmetic as Omit<ProfileBackgroundCosmetic, 'description' | 'obtainedAt'>;
|
||||
|
||||
return (
|
||||
<Card p="xs" withBorder>
|
||||
<Card p="xs" withBorder {...cardProps}>
|
||||
<Card.Section>
|
||||
<BackgroundImage
|
||||
sx={{
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'right',
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
src={
|
||||
backgroundImage && backgroundImage.data.url
|
||||
? getEdgeUrl(backgroundImage.data.url, {
|
||||
width: 'original',
|
||||
transcode: false,
|
||||
})
|
||||
: '/images/civitai-default-account-bg.png'
|
||||
}
|
||||
>
|
||||
<Stack spacing="xs" p="xs">
|
||||
<Group align="center" position="apart">
|
||||
<UserAvatar
|
||||
size="sm"
|
||||
avatarProps={{ size: 32 }}
|
||||
user={creator}
|
||||
subText={creator.createdAt ? `Joined ${formatDate(creator.createdAt)}` : undefined}
|
||||
withOverlay={!!backgroundImage}
|
||||
withUsername
|
||||
linkToProfile
|
||||
/>
|
||||
{withActions && (
|
||||
<Group spacing={8} noWrap>
|
||||
<TipBuzzButton
|
||||
toUserId={creator.id}
|
||||
size="xs"
|
||||
entityId={tipBuzzEntityId}
|
||||
label=""
|
||||
entityType={tipBuzzEntityType}
|
||||
styles={
|
||||
backgroundImage ? applyCosmeticThemeColors(backgroundImage.data) : undefined
|
||||
}
|
||||
compact
|
||||
/>
|
||||
<ChatUserButton
|
||||
user={creator}
|
||||
size="xs"
|
||||
label=""
|
||||
styles={
|
||||
backgroundImage ? applyCosmeticThemeColors(backgroundImage.data) : undefined
|
||||
}
|
||||
compact
|
||||
/>
|
||||
<FollowUserButton
|
||||
userId={creator.id}
|
||||
size="xs"
|
||||
styles={
|
||||
backgroundImage ? applyCosmeticThemeColors(backgroundImage.data) : undefined
|
||||
}
|
||||
compact
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
<Group spacing={8}>
|
||||
<RankBadge size="md" rank={creator.rank} />
|
||||
{stats && (
|
||||
<UserStatBadges
|
||||
uploads={uploads}
|
||||
followers={stats.followerCountAllTime}
|
||||
favorites={stats.thumbsUpCountAllTime}
|
||||
downloads={stats.downloadCountAllTime}
|
||||
colorOverrides={backgroundImage?.data}
|
||||
<Stack spacing="xs" p="xs">
|
||||
<Group align="center" position="apart">
|
||||
<UserAvatar
|
||||
size="sm"
|
||||
avatarProps={{ size: 32 }}
|
||||
user={creator}
|
||||
subText={creator.createdAt ? `Joined ${formatDate(creator.createdAt)}` : undefined}
|
||||
withUsername
|
||||
linkToProfile
|
||||
/>
|
||||
{withActions && (
|
||||
<Group spacing={8} noWrap>
|
||||
<TipBuzzButton
|
||||
toUserId={creator.id}
|
||||
size="xs"
|
||||
entityId={tipBuzzEntityId}
|
||||
label=""
|
||||
entityType={tipBuzzEntityType}
|
||||
compact
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</BackgroundImage>
|
||||
<ChatUserButton user={creator} size="xs" label="" compact />
|
||||
<FollowUserButton userId={creator.id} size="xs" compact />
|
||||
</Group>
|
||||
)}
|
||||
</Group>
|
||||
<Group spacing={8}>
|
||||
<RankBadge size="md" rank={creator.rank} />
|
||||
{stats && (
|
||||
<UserStatBadges
|
||||
uploads={uploads}
|
||||
followers={stats.followerCountAllTime}
|
||||
favorites={stats.thumbsUpCountAllTime}
|
||||
downloads={stats.downloadCountAllTime}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
{creator.links && creator.links.length > 0 ? (
|
||||
<Card.Section
|
||||
@@ -289,7 +251,7 @@ export const CreatorCardV2 = ({
|
||||
<Group spacing={8}>
|
||||
<RankBadge size="md" rank={creator.rank} />
|
||||
{stats && displayStats.length > 0 && (
|
||||
<UserStatBadges
|
||||
<UserStatBadgesV2
|
||||
uploads={displayStats.includes('uploads') ? uploads : null}
|
||||
followers={
|
||||
displayStats.includes('followers') ? stats.followerCountAllTime : null
|
||||
@@ -414,7 +376,7 @@ type Props = {
|
||||
tipBuzzEntityId?: number;
|
||||
tipBuzzEntityType?: string;
|
||||
withActions?: boolean;
|
||||
};
|
||||
} & Omit<CardProps, 'children'>;
|
||||
|
||||
type PropsV2 = {
|
||||
user: UserWithCosmetics;
|
||||
@@ -425,3 +387,13 @@ type PropsV2 = {
|
||||
useEquippedCosmetics?: boolean;
|
||||
startDisplayOverwrite?: string[];
|
||||
} & Omit<CardProps, 'children'>;
|
||||
|
||||
export const SmartCreatorCard = (props: PropsV2) => {
|
||||
const featureFlags = useFeatureFlags();
|
||||
|
||||
if (featureFlags.cosmeticShop) {
|
||||
return <CreatorCardV2 {...props} />;
|
||||
}
|
||||
|
||||
return <CreatorCard {...props} />;
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ export function IconBadge({ icon, children, tooltip, href, ...props }: IconBadge
|
||||
}
|
||||
|
||||
export type IconBadgeProps = {
|
||||
icon: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
tooltip?: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler<any> | undefined; //eslint-disable-line
|
||||
href?: string;
|
||||
|
||||
@@ -56,7 +56,7 @@ import { containerQuery } from '~/utils/mantine-css-helpers';
|
||||
import { ProfileBackgroundCosmetic } from '~/server/selectors/cosmetic.selector';
|
||||
import { getEdgeUrl } from '~/client-utils/cf-images-utils';
|
||||
import { applyCosmeticThemeColors } from '~/libs/sx-helpers';
|
||||
import { CreatorCardV2 } from '~/components/CreatorCard/CreatorCard';
|
||||
import { SmartCreatorCard } from '~/components/CreatorCard/CreatorCard';
|
||||
|
||||
export function ImageDetail() {
|
||||
const { classes, cx, theme } = useStyles();
|
||||
@@ -113,7 +113,7 @@ export function ImageDetail() {
|
||||
</div>
|
||||
<Card className={cx(classes.sidebar)}>
|
||||
<Card.Section withBorder>
|
||||
<CreatorCardV2 user={image.user} style={{ border: 0 }} />
|
||||
<SmartCreatorCard user={image.user} style={{ border: 0 }} />
|
||||
</Card.Section>
|
||||
<Card.Section
|
||||
py="xs"
|
||||
|
||||
@@ -44,7 +44,7 @@ import { AlertWithIcon } from '~/components/AlertWithIcon/AlertWithIcon';
|
||||
import { useCivitaiLink } from '~/components/CivitaiLink/CivitaiLinkProvider';
|
||||
import { CivitaiLinkManageButton } from '~/components/CivitaiLink/CivitaiLinkManageButton';
|
||||
import { ContentClamp } from '~/components/ContentClamp/ContentClamp';
|
||||
import { CreatorCard, CreatorCardV2 } from '~/components/CreatorCard/CreatorCard';
|
||||
import { CreatorCard, SmartCreatorCard } from '~/components/CreatorCard/CreatorCard';
|
||||
import {
|
||||
DescriptionTable,
|
||||
type Props as DescriptionTableProps,
|
||||
@@ -1026,7 +1026,11 @@ export function ModelVersionDetails({
|
||||
)}
|
||||
</Accordion>
|
||||
|
||||
<CreatorCardV2 user={model.user} tipBuzzEntityType="Model" tipBuzzEntityId={model.id} />
|
||||
<SmartCreatorCard
|
||||
user={model.user}
|
||||
tipBuzzEntityType="Model"
|
||||
tipBuzzEntityId={model.id}
|
||||
/>
|
||||
{onSite && (
|
||||
<Group
|
||||
align="flex-start"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Badge, Group, MantineSize, Text, useMantineTheme } from '@mantine/core';
|
||||
import { Badge, BadgeProps, Box, Group, MantineSize, Text, useMantineTheme } from '@mantine/core';
|
||||
import { IconUpload, IconUsers, IconDownload, IconChecks } from '@tabler/icons-react';
|
||||
|
||||
import { IconBadge } from '~/components/IconBadge/IconBadge';
|
||||
import { abbreviateNumber } from '~/utils/number-helpers';
|
||||
import { StatTooltip } from '~/components/Tooltips/StatTooltip';
|
||||
import { ThumbsUpIcon } from '~/components/ThumbsIcon/ThumbsIcon';
|
||||
import { EdgeMedia } from '~/components/EdgeMedia/EdgeMedia';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function UserStatBadges({
|
||||
followers,
|
||||
@@ -134,6 +136,53 @@ export function UserStatBadges({
|
||||
);
|
||||
}
|
||||
|
||||
const BadgedIcon = ({
|
||||
label,
|
||||
icon,
|
||||
value,
|
||||
size = 'md',
|
||||
textSize = 'xs',
|
||||
...props
|
||||
}: {
|
||||
label: string;
|
||||
value: number | string;
|
||||
icon: React.ReactNode;
|
||||
textSize?: MantineSize;
|
||||
} & Omit<BadgeProps, 'leftSection'>) => (
|
||||
<Group spacing={0} noWrap sx={{ position: 'relative' }}>
|
||||
<Box pos="relative" sx={{ zIndex: 2, overflow: 'hidden' }} h={32}>
|
||||
<Image src="/images/base-badge.png" alt={`${label} - ${value}`} width={32} height={32} />
|
||||
<Box
|
||||
style={{
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
position: 'absolute',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</Box>
|
||||
</Box>
|
||||
<IconBadge
|
||||
size={size}
|
||||
color="gray"
|
||||
// @ts-ignore
|
||||
variant="light"
|
||||
sx={{
|
||||
paddingLeft: 14,
|
||||
marginLeft: -14,
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<Text size={textSize} inline>
|
||||
{value}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
</Group>
|
||||
);
|
||||
|
||||
export function UserStatBadgesV2({
|
||||
followers,
|
||||
favorites,
|
||||
@@ -146,118 +195,42 @@ export function UserStatBadgesV2({
|
||||
const theme = useMantineTheme();
|
||||
|
||||
return (
|
||||
<Group spacing={8} position="apart">
|
||||
<Badge
|
||||
size="lg"
|
||||
radius="xl"
|
||||
px={8}
|
||||
color="dark"
|
||||
sx={
|
||||
colorOverrides
|
||||
? { backgroundColor: colorOverrides.backgroundColor ?? undefined }
|
||||
: undefined
|
||||
}
|
||||
variant={theme.colorScheme === 'dark' ? 'filled' : 'light'}
|
||||
>
|
||||
<Group spacing="xs" noWrap>
|
||||
{uploads != null ? (
|
||||
<IconBadge
|
||||
p={0}
|
||||
tooltip={<StatTooltip label="Uploads" value={uploads} />}
|
||||
icon={<IconUpload size={14} />}
|
||||
sx={
|
||||
colorOverrides
|
||||
? { color: colorOverrides.textColor ?? theme.colors.gray[0] }
|
||||
: undefined
|
||||
}
|
||||
size="lg"
|
||||
// @ts-ignore: transparent variant does work
|
||||
variant="transparent"
|
||||
>
|
||||
<Text size="xs" weight={600} inline>
|
||||
{abbreviateNumber(uploads)}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
) : null}
|
||||
{followers != null ? (
|
||||
<IconBadge
|
||||
p={0}
|
||||
tooltip={<StatTooltip label="Followers" value={followers} />}
|
||||
href={username ? `/user/${username}/followers` : undefined}
|
||||
icon={<IconUsers size={14} />}
|
||||
sx={
|
||||
colorOverrides
|
||||
? { color: colorOverrides.textColor ?? theme.colors.gray[0] }
|
||||
: undefined
|
||||
}
|
||||
size="lg"
|
||||
// @ts-ignore: transparent variant does work
|
||||
variant="transparent"
|
||||
>
|
||||
<Text size="xs" weight={600} inline>
|
||||
{abbreviateNumber(followers)}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
) : null}
|
||||
{favorites != null ? (
|
||||
<IconBadge
|
||||
p={0}
|
||||
tooltip={<StatTooltip label="Likes" value={favorites} />}
|
||||
icon={<ThumbsUpIcon size={14} />}
|
||||
sx={
|
||||
colorOverrides
|
||||
? { color: colorOverrides.textColor ?? theme.colors.gray[0] }
|
||||
: undefined
|
||||
}
|
||||
// @ts-ignore: transparent variant does work
|
||||
variant="transparent"
|
||||
size="lg"
|
||||
>
|
||||
<Text size="xs" weight={600} inline>
|
||||
{abbreviateNumber(favorites)}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
) : null}
|
||||
{downloads != null ? (
|
||||
<IconBadge
|
||||
p={0}
|
||||
tooltip={<StatTooltip label="Downloads" value={downloads} />}
|
||||
icon={<IconDownload size={14} />}
|
||||
sx={
|
||||
colorOverrides
|
||||
? { color: colorOverrides.textColor ?? theme.colors.gray[0] }
|
||||
: undefined
|
||||
}
|
||||
// @ts-ignore: transparent variant does work
|
||||
variant="transparent"
|
||||
size="lg"
|
||||
>
|
||||
<Text size="xs" weight={600} inline>
|
||||
{abbreviateNumber(downloads)}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
) : null}
|
||||
{answers != null && answers > 0 ? (
|
||||
<IconBadge
|
||||
p={0}
|
||||
tooltip={<StatTooltip label="Answers" value={answers} />}
|
||||
icon={<IconChecks size={14} />}
|
||||
sx={
|
||||
colorOverrides
|
||||
? { color: colorOverrides.textColor ?? theme.colors.gray[0] }
|
||||
: undefined
|
||||
}
|
||||
// @ts-ignore: transparent variant does work
|
||||
variant="transparent"
|
||||
size="lg"
|
||||
>
|
||||
<Text size="xs" weight={600} inline>
|
||||
{abbreviateNumber(answers)}
|
||||
</Text>
|
||||
</IconBadge>
|
||||
) : null}
|
||||
</Group>
|
||||
</Badge>
|
||||
<Group spacing="xs" noWrap>
|
||||
{uploads != null ? (
|
||||
<BadgedIcon
|
||||
icon={<IconUpload size={18} color="white" />}
|
||||
label="Uploads"
|
||||
value={abbreviateNumber(uploads)}
|
||||
/>
|
||||
) : null}
|
||||
{followers != null ? (
|
||||
<BadgedIcon
|
||||
icon={<IconUsers size={18} color="white" />}
|
||||
label="Followers"
|
||||
value={abbreviateNumber(followers)}
|
||||
/>
|
||||
) : null}
|
||||
{favorites != null ? (
|
||||
<BadgedIcon
|
||||
icon={<ThumbsUpIcon size={18} color="white" />}
|
||||
label="Likes"
|
||||
value={abbreviateNumber(favorites)}
|
||||
/>
|
||||
) : null}
|
||||
{downloads != null ? (
|
||||
<BadgedIcon
|
||||
icon={<IconDownload size={18} color="white" />}
|
||||
label="Downloads"
|
||||
value={abbreviateNumber(downloads)}
|
||||
/>
|
||||
) : null}
|
||||
{answers != null && answers > 0 ? (
|
||||
<BadgedIcon
|
||||
icon={<IconChecks size={18} color="white" />}
|
||||
label="Answers"
|
||||
value={abbreviateNumber(answers)}
|
||||
/>
|
||||
) : null}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ import { IconDotsVertical } from '@tabler/icons-react';
|
||||
import { ReportMenuItem } from '~/components/MenuItems/ReportMenuItem';
|
||||
import { ReportEntity } from '~/server/schema/report.schema';
|
||||
import { openContext } from '~/providers/CustomModalsProvider';
|
||||
import { CreatorCardV2 } from '~/components/CreatorCard/CreatorCard';
|
||||
import { SmartCreatorCard } from '~/components/CreatorCard/CreatorCard';
|
||||
import { formatDate } from '~/utils/date-helpers';
|
||||
import { TrackView } from '~/components/TrackView/TrackView';
|
||||
import { RenderHtml } from '~/components/RenderHtml/RenderHtml';
|
||||
@@ -262,7 +262,7 @@ export default function BountyEntryDetailsPage({
|
||||
)}
|
||||
</NavigateBack>
|
||||
</Group>
|
||||
<CreatorCardV2 user={user} />
|
||||
<SmartCreatorCard user={user} />
|
||||
</Stack>
|
||||
</Card.Section>
|
||||
)}
|
||||
|
||||
@@ -89,6 +89,7 @@ const featureFlags = createFeatureFlags({
|
||||
vault: ['mod'],
|
||||
draftMode: ['mod'],
|
||||
membershipsV2: ['mod'],
|
||||
cosmeticShop: ['mod'],
|
||||
});
|
||||
export const featureFlagKeys = Object.keys(featureFlags) as FeatureFlagKey[];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user