Introduce user card component
authorMarcel Werk <burntime@woltlab.com>
Tue, 9 Jan 2024 14:36:26 +0000 (15:36 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 9 Jan 2024 14:36:26 +0000 (15:36 +0100)
com.woltlab.wcf/templates/userCard.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/UserProfile.class.php
wcfsetup/install/files/style/ui/userCard.scss [new file with mode: 0644]

diff --git a/com.woltlab.wcf/templates/userCard.tpl b/com.woltlab.wcf/templates/userCard.tpl
new file mode 100644 (file)
index 0000000..cb644be
--- /dev/null
@@ -0,0 +1,112 @@
+<div class="userCard">
+       <div class="userCard__header">
+               <div class="userCard__header__background">
+                       <img
+                               class="userCard__header__background__image"
+                               src="{$user->getCoverPhoto()->getURL()}"
+                               loading="lazy">
+               </div>
+               <div class="userCard__header__avatar">
+                       {user object=$user type='avatar64' ariaHidden='true' tabindex='-1'}
+
+                       {if $user->isOnline()}<span class="userCard__onlineIndicator jsTooltip" title="{lang username=$user->username}wcf.user.online.title{/lang}"></span>{/if}
+               </div>
+       </div>
+
+       <div class="userCard__content">
+               <h3 class="userCard__username">
+                       {user object=$user class='username'}
+
+                       {if $user->banned}
+                               <span class="jsTooltip jsUserBanned" title="{lang}wcf.user.banned{/lang}">
+                                       {icon name='lock'}
+                               </span>
+                       {/if}
+               </h3>
+
+               {if MODULE_USER_RANK}
+                       {hascontent}
+                               <div class="userCard__title">
+                                       {content}
+                                               {if $user->getUserTitle()}
+                                                       <span class="badge userTitleBadge{if $user->getRank() && $user->getRank()->cssClassName} {@$user->getRank()->cssClassName}{/if}">{$user->getUserTitle()}</span>
+                                               {/if}
+                                               {if $user->getRank() && $user->getRank()->rankImage}
+                                                       <span class="userRankImage">{@$user->getRank()->getImage()}</span>
+                                               {/if}
+                                       {/content}
+                               </div>
+                       {/hascontent}
+               {/if}
+
+               {hascontent}
+                       <div class="userCard__buttons">
+                               {content}
+                                       {if $user->homepage && $user->homepage != 'http://'}
+                                               <a class="userCard__button jsTooltip" title="{lang}wcf.user.option.homepage{/lang}" {anchorAttributes url=$user->homepage appendClassname=false isUgc=true}>{icon name='house' size=24}</a>
+                                       {/if}
+                                       {if $user->userID != $__wcf->user->userID}
+                                               {if $user->isAccessible('canViewEmailAddress')}
+                                                       <a class="userCard__button jsTooltip" href="mailto:{@$user->getEncodedEmail()}" title="{lang}wcf.user.button.mail{/lang}">{icon name='envelope' size=24}</a>
+                                               {/if}
+                                       {/if}
+                                       {if $__wcf->user->userID && $user->userID != $__wcf->user->userID}
+                                               {if !$__wcf->getUserProfileHandler()->isIgnoredByUser($user->userID)}
+                                                       {if $__wcf->getUserProfileHandler()->isFollowing($user->userID)}
+                                                               <button
+                                                                       type="button"
+                                                                       data-following="1"
+                                                                       data-follow-user="{link controller='UserFollow' id=$user->userID}{/link}"
+                                                                       class="userCard__button jsTooltip"
+                                                                       title="{lang}wcf.user.button.unfollow{/lang}"
+                                                               >{icon name='circle-minus' size=24}</button>
+                                                       {else}
+                                                               <button
+                                                                       type="button"
+                                                                       data-following="0"
+                                                                       data-follow-user="{link controller='UserFollow' id=$user->userID}{/link}"
+                                                                       class="userCard__button jsTooltip"
+                                                                       title="{lang}wcf.user.button.follow{/lang}"
+                                                               >{icon name='circle-plus' size=24}</button>
+                                                       {/if}
+                                               {/if}
+                                       {/if}
+                                       {event name='buttons'}
+                               {/content}
+                       </div>
+               {/hascontent}
+       </div>
+
+       {hascontent}
+               <div class="userCard__footer">
+                       <ul class="userCard__footer__stats">
+                               {content}
+                                       {event name='beforeStats'}
+                                       
+                                       {if MODULE_LIKE && $user->likesReceived}
+                                               <li class="userCard__footer__statsItem">
+                                                       <span class="userCard__footer__statsItem__key">{lang}wcf.like.reactionsReceived{/lang}</span>
+                                                       <span class="userCard__footer__statsItem__value">{#$user->likesReceived}</span>
+                                               </li>
+                                       {/if}
+
+                                       {if $user->activityPoints}
+                                               <li class="userCard__footer__statsItem">
+                                                       <span class="userCard__footer__statsItem__key">{lang}wcf.user.activityPoint{/lang}</span>
+                                                       <span class="userCard__footer__statsItem__value">{#$user->activityPoints}</span>
+                                               </li>
+                                       {/if}
+
+                                       {if $user->showTrophyPoints()}
+                                               <li class="userCard__footer__statsItem">
+                                                       <span class="userCard__footer__statsItem__key">{lang}wcf.user.trophy.trophyPoints{/lang}</span>
+                                                       <span class="userCard__footer__statsItem__value">{#$user->trophyPoints}</span>
+                                               </li>
+                                       {/if}
+
+                                       {event name='afterStats'}
+                               {/content}
+                       </ul>
+               </div>
+       {/hascontent}
+</div>
index 31fc2cff35ffc70dc0d16bab5ef8918f9923463a..f994c611ae3bd2deed8f6bed43857c5b787fc0f0 100644 (file)
@@ -1198,4 +1198,15 @@ class UserProfile extends DatabaseObjectDecorator implements ITitledLinkObject
     {
         return new self(new User(null, ['username' => $username]));
     }
+
+    /**
+     * @since 6.1
+     */
+    public function showTrophyPoints(): bool
+    {
+        return MODULE_TROPHY
+            && WCF::getSession()->getPermission('user.profile.trophy.canSeeTrophies')
+            && $this->trophyPoints
+            && ($this->isAccessible('canViewTrophies') || $this->userID == WCF::getSession()->userID);
+    }
 }
diff --git a/wcfsetup/install/files/style/ui/userCard.scss b/wcfsetup/install/files/style/ui/userCard.scss
new file mode 100644 (file)
index 0000000..5d0f6c2
--- /dev/null
@@ -0,0 +1,130 @@
+.userCardList {
+    display: grid;
+    gap: 20px;
+    grid-auto-rows: minmax(200px, auto);
+    padding: 20px 0;
+
+    @include screen-xl {
+        grid-template-columns: repeat(3, 1fr);
+    }
+
+    @include screen-lg-only {
+        grid-template-columns: repeat(2, 1fr);
+    }
+
+    @include screen-md {
+        grid-template-columns: repeat(3, 1fr);
+    }
+
+    @include screen-sm {
+        grid-template-columns: repeat(2, 1fr);
+    }
+}
+
+.userCard {
+    background-color: var(--wcfContentBackground);
+    border-radius: var(--wcfBorderRadius);
+    box-shadow: var(--wcfBoxShadowCard);
+    display: flex;
+    flex-direction: column;
+    position: relative;
+}
+
+.userCard__header {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+}
+
+.userCard__header__background {
+    height: 100px;
+    overflow: hidden;
+    border-top-right-radius: var(--wcfBorderRadius);
+    border-top-left-radius: var(--wcfBorderRadius);
+}
+
+.userCard__header__background__image {
+    height: 100%;
+    object-fit: cover;
+    max-width: 100%;
+}
+
+.userCard__header__avatar {
+    position: relative;
+    border-radius: 50%;
+    border: 5px solid var(--wcfContentBackground);
+    margin: auto;
+    margin-top: -37px;
+}
+
+.userCard__onlineIndicator {
+    background-color: rgba(0, 153, 0, 1);
+    border: 1px solid var(--wcfContentBackground);
+    border-radius: 50%;
+    bottom: 0;
+    height: 12px;
+    position: absolute;
+    right: 6px;
+    width: 12px;
+}
+
+.userCard__footer {
+    border-top: 1px solid var(--wcfContentBorderInner);
+    padding: 10px;
+}
+
+.userCard__content {
+    text-align: center;
+    display: flex;
+    flex-direction: column;
+    flex: 1 auto;
+    row-gap: 10px;
+    padding: 20px;
+}
+
+.userCard__username {
+    @include wcfFontHeadline;
+    @include wcfFontBold;
+
+    a {
+        color: inherit;
+    }
+}
+
+.userCard__footer__stats {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 10px;
+}
+
+.userCard__footer__statsItem {
+    display: flex;
+    flex-direction: column;
+    text-align: center;
+}
+
+.userCard__footer__statsItem:nth-child(n + 4) {
+    display: none;
+}
+
+.userCard__footer__statsItem__key {
+    @include wcfFontSmall;
+    color: var(--wcfContentDimmedText);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.userCard__buttons {
+    background-color: var(--wcfContentContainerBackground);
+    border-radius: 30px;
+    display: flex;
+    justify-content: center;
+    gap: 5px;
+    margin: 0 auto;
+    padding: 5px 10px;
+
+    a {
+        color: inherit;
+    }
+}