From 881c717f50cf478f4943741d758cbaa8e9ba112b Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 19 Feb 2022 21:46:44 -0800 Subject: [PATCH 1/5] Cache open Project Loved forum polls --- .env.example | 1 + app/Libraries/ProjectLovedPollsCache.php | 49 ++++++++++++++++++++++++ app/Listeners/OctaneResetLocalCache.php | 1 + app/Models/Forum/TopicPoll.php | 10 +++-- app/Providers/AppServiceProvider.php | 3 ++ config/osu.php | 1 + 6 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 app/Libraries/ProjectLovedPollsCache.php diff --git a/.env.example b/.env.example index 485130b484c..f6e4206c2d2 100644 --- a/.env.example +++ b/.env.example @@ -88,6 +88,7 @@ SLACK_ENDPOINT=https://myconan.net/null/ # INITIAL_HELP_FORUM_IDS="5 47 85" # ISSUE_FORUM_IDS= # FORUM_POST_MINIMUM_PLAYS=200 +# PROJECT_LOVED_FORUM_ID=120 # GA_TRACKING_ID=UA-xxx diff --git a/app/Libraries/ProjectLovedPollsCache.php b/app/Libraries/ProjectLovedPollsCache.php new file mode 100644 index 00000000000..b90e03cb2e1 --- /dev/null +++ b/app/Libraries/ProjectLovedPollsCache.php @@ -0,0 +1,49 @@ +. Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +declare(strict_types=1); + +namespace App\Libraries; + +use App\Models\Forum\Topic; +use App\Models\User; +use App\Traits\LocallyCached; +use Carbon\Carbon; +use Illuminate\Database\Eloquent\Collection; + +class ProjectLovedPollsCache +{ + use LocallyCached; + + /** + * Get all topics on the Project Loved forum that have an open poll and finite poll length. + */ + public function all(): Collection + { + $topics = $this->cachedMemoize(__FUNCTION__, function () { + return Topic + ::where('forum_id', config('osu.forum.project_loved_forum_id')) + ->whereRaw('poll_start + poll_length > ?', [Carbon::now()->getTimestamp()]) + ->get(); + }); + + if ($topics->contains(fn (Topic $topic) => !$topic->poll()->isOpen())) { + $this->resetCache(); + return $this->all(); + } + + return $topics; + } + + /** + * Check if the user voted in any open Project Loved polls. + */ + public function userVotedAny(User $user): bool + { + return $this->memoize(__FUNCTION__.':'.$user->getKey(), function () use ($user) { + return $this->all()->contains(fn (Topic $topic) => $topic->poll()->votedBy($user)); + }); + } +} diff --git a/app/Listeners/OctaneResetLocalCache.php b/app/Listeners/OctaneResetLocalCache.php index f7f3a3eac03..92918e8463e 100644 --- a/app/Listeners/OctaneResetLocalCache.php +++ b/app/Listeners/OctaneResetLocalCache.php @@ -11,5 +11,6 @@ public function handle($event): void { app('chat-filters')->incrementResetTicker(); app('groups')->incrementResetTicker(); + app('loved-polls')->incrementResetTicker(); } } diff --git a/app/Models/Forum/TopicPoll.php b/app/Models/Forum/TopicPoll.php index e5c6adac595..6d9bb755da8 100644 --- a/app/Models/Forum/TopicPoll.php +++ b/app/Models/Forum/TopicPoll.php @@ -106,7 +106,7 @@ public function save() return false; } - return DB::transaction(function () { + DB::transaction(function () { $this->topic->update([ 'poll_title' => $this->params['title'], 'poll_start' => Carbon::now(), @@ -133,9 +133,13 @@ public function save() 'poll_option_text' => $value, ]); } - - return true; }); + + if ($this->topic->forum_id === config('osu.forum.project_loved_forum_id')) { + app('loved-polls')->resetCache(); + } + + return true; } public function setTopic($topic) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 61cef4dbc74..9c8b76774d2 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -14,6 +14,7 @@ use App\Libraries\OsuAuthorize; use App\Libraries\OsuCookieJar; use App\Libraries\OsuMessageSelector; +use App\Libraries\ProjectLovedPollsCache; use App\Libraries\RouteSection; use App\Libraries\User\ScorePins; use Datadog; @@ -34,6 +35,7 @@ class AppServiceProvider extends ServiceProvider 'assets-manifest' => AssetsManifest::class, 'chat-filters' => ChatFilters::class, 'groups' => Groups::class, + 'loved-polls' => ProjectLovedPollsCache::class, 'route-section' => RouteSection::class, 'score-pins' => ScorePins::class, ]; @@ -55,6 +57,7 @@ public function boot() app('OsuAuthorize')->resetCache(); app('groups')->incrementResetTicker(); app('chat-filters')->incrementResetTicker(); + app('loved-polls')->incrementResetTicker(); Datadog::increment( config('datadog-helper.prefix_web').'.queue.run', diff --git a/config/osu.php b/config/osu.php index 4cc86fd1593..641c32962af 100644 --- a/config/osu.php +++ b/config/osu.php @@ -107,6 +107,7 @@ 'minimum_plays' => get_int(env('FORUM_POST_MINIMUM_PLAYS')) ?? 200, 'necropost_months' => 6, 'poll_edit_hours' => get_int(env('FORUM_POLL_EDIT_HOURS')) ?? 1, + 'project_loved_forum_id' => get_int(env('PROJECT_LOVED_FORUM_ID')) ?? 120, 'double_post_time' => [ 'author' => 24, From a0778a3b0f5d77b0e7124d929d3d752ea56d4d76 Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 19 Feb 2022 21:48:16 -0800 Subject: [PATCH 2/5] Include Loved voted status in transformer for profile pages --- app/Http/Controllers/UsersController.php | 1 + app/Transformers/UserCompactTransformer.php | 7 +++++++ resources/views/docs/_structures/user_compact.md | 1 + 3 files changed, 9 insertions(+) diff --git a/app/Http/Controllers/UsersController.php b/app/Http/Controllers/UsersController.php index 66f76c62a79..f6f17397a75 100644 --- a/app/Http/Controllers/UsersController.php +++ b/app/Http/Controllers/UsersController.php @@ -498,6 +498,7 @@ public function me($mode = null) * - statistics.variants * - support_level * - user_achievements + * - voted_in_project_loved * * @urlParam user integer required Id or username of the user. Id lookup is prioritised unless `key` parameter is specified. Previous usernames are also checked in some cases. Example: 1 * @urlParam mode string [GameMode](#gamemode). User default mode will be used if not specified. Example: osu diff --git a/app/Transformers/UserCompactTransformer.php b/app/Transformers/UserCompactTransformer.php index 6e632d1e35d..72326201504 100644 --- a/app/Transformers/UserCompactTransformer.php +++ b/app/Transformers/UserCompactTransformer.php @@ -39,6 +39,7 @@ class UserCompactTransformer extends TransformerAbstract 'mapping_follower_count', 'previous_usernames', 'support_level', + 'voted_in_project_loved', ]; protected string $mode; @@ -85,6 +86,7 @@ class UserCompactTransformer extends TransformerAbstract 'unread_pm_count', 'user_achievements', 'user_preferences', + 'voted_in_project_loved', // TODO: should be changed to rank_history // TODO: should be alphabetically ordered but lazer relies on being after statistics. can revert to alphabetical after 2020-05-01 'rankHistory', @@ -427,6 +429,11 @@ public function includeUserPreferences(User $user) ])); } + public function includeVotedInProjectLoved(User $user) + { + return $this->primitive(app('loved-polls')->userVotedAny($user)); + } + public function setMode(string $mode) { $this->mode = $mode; diff --git a/resources/views/docs/_structures/user_compact.md b/resources/views/docs/_structures/user_compact.md index 5cdc7367866..067a26a4626 100644 --- a/resources/views/docs/_structures/user_compact.md +++ b/resources/views/docs/_structures/user_compact.md @@ -70,6 +70,7 @@ support_level | | unread_pm_count | | user_achievements | | user_preferences | | +voted_in_project_loved | boolean
From f75bbf44044681f86214d7434367035b8b18adab Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 19 Feb 2022 21:52:41 -0800 Subject: [PATCH 3/5] Add UserProfileJson for use in profile pages --- resources/assets/lib/interfaces/user-json.ts | 1 + .../lib/interfaces/user-profile-json.ts | 20 +++++++++++++++++++ .../lib/profile-page/extra-page-props.ts | 13 +++--------- .../lib/user-multiplayer-index/header.tsx | 4 ++-- .../lib/user-multiplayer-index/main.tsx | 4 ++-- 5 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 resources/assets/lib/interfaces/user-profile-json.ts diff --git a/resources/assets/lib/interfaces/user-json.ts b/resources/assets/lib/interfaces/user-json.ts index 55ccc42ae6c..7a0c4d11e2c 100644 --- a/resources/assets/lib/interfaces/user-json.ts +++ b/resources/assets/lib/interfaces/user-json.ts @@ -62,6 +62,7 @@ interface UserJsonAvailableIncludes { unread_pm_count: number; user_achievements: UserAchievementJson[]; user_preferences: UserPreferencesJson; + voted_in_project_loved: boolean; } interface UserJsonDefaultAttributes { diff --git a/resources/assets/lib/interfaces/user-profile-json.ts b/resources/assets/lib/interfaces/user-profile-json.ts new file mode 100644 index 00000000000..fd7e3dc5a61 --- /dev/null +++ b/resources/assets/lib/interfaces/user-profile-json.ts @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +import UserExtendedJson from './user-extended-json'; +import UserJson from './user-json'; + +type UserProfileHeaderIncludes = + | 'active_tournament_banner' + | 'badges' + | 'comments_count' + | 'follower_count' + | 'groups' + | 'mapping_follower_count' + | 'previous_usernames' + | 'support_level' + | 'voted_in_project_loved'; + +type UserProfileJson = UserExtendedJson & Required>; + +export default UserProfileJson; diff --git a/resources/assets/lib/profile-page/extra-page-props.ts b/resources/assets/lib/profile-page/extra-page-props.ts index dc416f24f7f..2ca781c866e 100644 --- a/resources/assets/lib/profile-page/extra-page-props.ts +++ b/resources/assets/lib/profile-page/extra-page-props.ts @@ -1,7 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. -import UserExtendedJson from 'interfaces/user-extended-json'; +import UserJson from 'interfaces/user-json'; +import UserProfileJson from 'interfaces/user-profile-json'; import Controller from './controller'; export const beatmapsetSections = [ @@ -22,20 +23,13 @@ export type HistoricalSection = typeof historicalSections[number]; type ProfilePageIncludes = 'account_history' - | 'active_tournament_banner' - | 'badges' | 'beatmap_playcounts_count' - | 'comments_count' | 'favourite_beatmapset_count' - | 'follower_count' | 'graveyard_beatmapset_count' - | 'groups' | 'loved_beatmapset_count' - | 'mapping_follower_count' | 'monthly_playcounts' | 'page' | 'pending_beatmapset_count' - | 'previous_usernames' | 'rank_history' | 'ranked_beatmapset_count' | 'replays_watched_counts' @@ -44,10 +38,9 @@ type ProfilePageIncludes = | 'scores_pinned_count' | 'scores_recent_count' | 'statistics' - | 'support_level' | 'user_achievements'; -export type ProfilePageUserJson = UserExtendedJson & Required>; +export type ProfilePageUserJson = UserProfileJson & Required>; export const profilePageSections = [...beatmapsetSections, ...topScoreSections, ...historicalSections, 'recentActivity', 'recentlyReceivedKudosu'] as const; export type ProfilePageSection = typeof profilePageSections[number]; diff --git a/resources/assets/lib/user-multiplayer-index/header.tsx b/resources/assets/lib/user-multiplayer-index/header.tsx index f95ad552cb7..790e5075677 100644 --- a/resources/assets/lib/user-multiplayer-index/header.tsx +++ b/resources/assets/lib/user-multiplayer-index/header.tsx @@ -3,8 +3,8 @@ import HeaderV4 from 'components/header-v4'; import ProfileTournamentBanner from 'components/profile-tournament-banner'; -import UserExtendedJson from 'interfaces/user-extended-json'; import { MultiplayerTypeGroup } from 'interfaces/user-multiplayer-history-json'; +import UserProfileJson from 'interfaces/user-profile-json'; import Badges from 'profile-page/badges'; import Detail from 'profile-page/detail'; import HeaderInfo from 'profile-page/header-info'; @@ -14,7 +14,7 @@ import * as React from 'react'; interface Props { typeGroup: MultiplayerTypeGroup; - user: UserExtendedJson; + user: UserProfileJson; } export default class Header extends React.Component { diff --git a/resources/assets/lib/user-multiplayer-index/main.tsx b/resources/assets/lib/user-multiplayer-index/main.tsx index 4bd69fe46b8..d61a66b0e7e 100644 --- a/resources/assets/lib/user-multiplayer-index/main.tsx +++ b/resources/assets/lib/user-multiplayer-index/main.tsx @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. import UserProfileContainer from 'components/user-profile-container'; -import UserExtendedJson from 'interfaces/user-extended-json'; +import UserProfileJson from 'interfaces/user-profile-json'; import * as React from 'react'; import Header from 'user-multiplayer-index/header'; import MultiplayerHistory from 'user-multiplayer-index/multiplayer-history'; @@ -10,7 +10,7 @@ import MultiplayerHistoryStore from './multiplayer-history-store'; interface Props { store: MultiplayerHistoryStore; - user: UserExtendedJson; + user: UserProfileJson; } export default function Main(props: Props) { From 47824ce998f4ff95eff7f0a5439db27e23b76b49 Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 19 Feb 2022 21:54:48 -0800 Subject: [PATCH 4/5] Add "I voted!" Loved sticker and refactor profile header banners --- resources/assets/less/bem-index.less | 3 +- .../less/bem/profile-header-banner.less | 47 +++++++++++++++++++ .../less/bem/profile-header-banners.less | 11 +++++ .../less/bem/profile-tournament-banner.less | 27 ----------- resources/assets/less/variables.less | 1 - .../lib/components/profile-header-banners.tsx | 41 ++++++++++++++++ .../components/profile-tournament-banner.tsx | 24 ---------- .../assets/lib/modding-profile/header.coffee | 5 +- resources/assets/lib/profile-page/header.tsx | 4 +- .../lib/user-multiplayer-index/header.tsx | 4 +- resources/lang/en/users.php | 4 ++ 11 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 resources/assets/less/bem/profile-header-banner.less create mode 100644 resources/assets/less/bem/profile-header-banners.less delete mode 100644 resources/assets/less/bem/profile-tournament-banner.less create mode 100644 resources/assets/lib/components/profile-header-banners.tsx delete mode 100644 resources/assets/lib/components/profile-tournament-banner.tsx diff --git a/resources/assets/less/bem-index.less b/resources/assets/less/bem-index.less index cd274f38f71..643fac75961 100644 --- a/resources/assets/less/bem-index.less +++ b/resources/assets/less/bem-index.less @@ -296,7 +296,8 @@ @import "bem/profile-previous-usernames"; @import "bem/profile-rank-count"; @import "bem/profile-stats"; -@import "bem/profile-tournament-banner"; +@import "bem/profile-header-banner"; +@import "bem/profile-header-banners"; @import "bem/proportional-container"; @import "bem/qtip"; @import "bem/quick-info"; diff --git a/resources/assets/less/bem/profile-header-banner.less b/resources/assets/less/bem/profile-header-banner.less new file mode 100644 index 00000000000..1ecd9b7018f --- /dev/null +++ b/resources/assets/less/bem/profile-header-banner.less @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +.profile-header-banner { + @_top: profile-header-banner; + overflow: hidden; + + @media @desktop { + .own-layer(); + transition: transform .2s cubic-bezier(.08,.82,.17,1); + + &:hover { + transform: scale(1.1); + z-index: 1; + } + + & + & { + margin-left: 10px; + } + } + + &--loved-voted-sticker { + .default-border-radius(); + .link-plain(); + .link-white(); + background-color: @osu-colour-b6; + display: flex; + flex-direction: row-reverse; + align-items: center; + padding: 4px 0 4px 10px; + + .@{_top}__image { + margin-left: -10px; + } + } + + &--tournament { + @media @desktop { + .default-border-radius(); + width: 60%; + } + + .@{_top}__image { + width: 100%; + } + } +} diff --git a/resources/assets/less/bem/profile-header-banners.less b/resources/assets/less/bem/profile-header-banners.less new file mode 100644 index 00000000000..edc3da5a55b --- /dev/null +++ b/resources/assets/less/bem/profile-header-banners.less @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +.profile-header-banners { + display: flex; + align-items: end; + + @media @desktop { + margin-bottom: 10px; + } +} diff --git a/resources/assets/less/bem/profile-tournament-banner.less b/resources/assets/less/bem/profile-tournament-banner.less deleted file mode 100644 index a141a4ebd92..00000000000 --- a/resources/assets/less/bem/profile-tournament-banner.less +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -.profile-tournament-banner { - .own-layer(); - transition: transform .2s cubic-bezier(.08,.82,.17,1); - - @media @desktop { - position: absolute; - bottom: 100%; - margin-bottom: 10px; - left: 0; - width: 60%; - - &:hover { - transform: scale(1.1); - } - } - - &__image { - width: 100%; - - @media @desktop { - .default-border-radius(); - } - } -} diff --git a/resources/assets/less/variables.less b/resources/assets/less/variables.less index ecc98c23c5a..e53100141d2 100644 --- a/resources/assets/less/variables.less +++ b/resources/assets/less/variables.less @@ -93,7 +93,6 @@ @z-index--beatmaps-panel: 4; @z-index--profile-previous-usernames: 10; -@z-index--profile-tournament-banner: 11; @z-index--page-extra-tabs: 99; @z-index--profile-page-cover-selector: 100; // must be at least 1 more than @z-index--page-extra-tabs @z-index--beatmap-discussion-editor-insertion-menu: 100; diff --git a/resources/assets/lib/components/profile-header-banners.tsx b/resources/assets/lib/components/profile-header-banners.tsx new file mode 100644 index 00000000000..d5622ff7800 --- /dev/null +++ b/resources/assets/lib/components/profile-header-banners.tsx @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. +// See the LICENCE file in the repository root for full licence text. + +import Img2x from 'components/img2x'; +import UserProfileJson from 'interfaces/user-profile-json'; +import { route } from 'laroute'; +import * as React from 'react'; + +interface Props { + user: UserProfileJson; +} + +export default function ProfileHeaderBanners({ user }: Props) { + const { + active_tournament_banner: tournamentBanner, + voted_in_project_loved: votedInProjectLoved, + } = user; + + return ( +
+ {tournamentBanner != null && ( + + + + )} + {votedInProjectLoved && ( + + + {osu.trans('users.show.loved_voted_sticker.content')} + + )} +
+ ); +} diff --git a/resources/assets/lib/components/profile-tournament-banner.tsx b/resources/assets/lib/components/profile-tournament-banner.tsx deleted file mode 100644 index b903a7942a1..00000000000 --- a/resources/assets/lib/components/profile-tournament-banner.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -import Img2x from 'components/img2x'; -import ProfileBannerJson from 'interfaces/profile-banner'; -import { route } from 'laroute'; -import * as React from 'react'; - -interface Props { - banner?: ProfileBannerJson | null; -} - -export default function ProfileTournamentBanner({ banner }: Props) { - if (banner == null) return null; - - return ( - - - - ); -} diff --git a/resources/assets/lib/modding-profile/header.coffee b/resources/assets/lib/modding-profile/header.coffee index 10b7b58db26..f9ef3ff54f0 100644 --- a/resources/assets/lib/modding-profile/header.coffee +++ b/resources/assets/lib/modding-profile/header.coffee @@ -3,7 +3,7 @@ import HeaderV4 from 'components/header-v4' import Img2x from 'components/img2x' -import ProfileTournamentBanner from 'components/profile-tournament-banner' +import ProfileHeaderBanners from 'components/profile-header-banners' import Badges from 'profile-page/badges' import Detail from 'profile-page/detail' import HeaderInfo from 'profile-page/header-info' @@ -22,8 +22,7 @@ export class Header extends React.Component 'data-page-id': 'main' el HeaderV4, backgroundImage: @props.user.cover.url - contentPrepend: el ProfileTournamentBanner, - banner: @props.user.active_tournament_banner + contentPrepend: el ProfileHeaderBanners, user: @props.user links: headerLinks(@props.user, 'modding') theme: 'users' div className: 'osu-page osu-page--users', diff --git a/resources/assets/lib/profile-page/header.tsx b/resources/assets/lib/profile-page/header.tsx index 3c0bc276cd4..c49223a26e3 100644 --- a/resources/assets/lib/profile-page/header.tsx +++ b/resources/assets/lib/profile-page/header.tsx @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. import HeaderV4 from 'components/header-v4'; -import ProfileTournamentBanner from 'components/profile-tournament-banner'; +import ProfileHeaderBanners from 'components/profile-header-banners'; import { action, observable, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import { isModalShowing } from 'modal-helper'; @@ -53,7 +53,7 @@ export default class Header extends React.Component {
} + contentPrepend={} isCoverUpdating={this.props.controller.isUpdatingCover} links={headerLinks(this.props.controller.state.user, 'show')} theme='users' diff --git a/resources/assets/lib/user-multiplayer-index/header.tsx b/resources/assets/lib/user-multiplayer-index/header.tsx index 790e5075677..a4d1f9cd77d 100644 --- a/resources/assets/lib/user-multiplayer-index/header.tsx +++ b/resources/assets/lib/user-multiplayer-index/header.tsx @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. import HeaderV4 from 'components/header-v4'; -import ProfileTournamentBanner from 'components/profile-tournament-banner'; +import ProfileHeaderBanners from 'components/profile-header-banners'; import { MultiplayerTypeGroup } from 'interfaces/user-multiplayer-history-json'; import UserProfileJson from 'interfaces/user-profile-json'; import Badges from 'profile-page/badges'; @@ -23,7 +23,7 @@ export default class Header extends React.Component {
} + contentPrepend={} links={headerLinks(this.props.user, this.props.typeGroup)} theme='users' /> diff --git a/resources/lang/en/users.php b/resources/lang/en/users.php index a5f8b6e0825..a0e02884f67 100644 --- a/resources/lang/en/users.php +++ b/resources/lang/en/users.php @@ -376,6 +376,10 @@ 'twitter' => 'Twitter', 'website' => 'Website', ], + 'loved_voted_sticker' => [ + 'content' => 'I voted!', + 'title' => ':username voted in the latest round of Project Loved!', + ], 'not_found' => [ 'reason_1' => 'They may have changed their username.', 'reason_2' => 'The account may be temporarily unavailable due to security or abuse issues.', From 797ffdcc403e5a7689f43d062f34e7cab671cabc Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 19 Feb 2022 21:55:23 -0800 Subject: [PATCH 5/5] Add sticker image (replace this commit later) --- public/images/layout/loved-voted-sticker.png | Bin 0 -> 1932 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/layout/loved-voted-sticker.png diff --git a/public/images/layout/loved-voted-sticker.png b/public/images/layout/loved-voted-sticker.png new file mode 100644 index 0000000000000000000000000000000000000000..f25c4036e67b8360b060e758a14638b8f0b7d0b8 GIT binary patch literal 1932 zcmV;72Xpv|P)^#TCcD=gi#u+u!fE%L)oxDrKR-Do~nMh+0hp zHrPc9ia<5=p{B&NrL;aMCe^Z0thGU@ZK`gYTBNC1lu}A~NYqlb@qwj^R1mkkL`z#( zVclhak9%k4+do`sB)cMygk2}OxsS{_bMl!pXXczM=!WgJ+m~u4%3KJB2}(Kd&;d6f zTbZl<@0$nqwH$0~$yHYG?Nw9p%4z-LC&l8a!8cs$PR6*>ogn;k!Sal%UK`SIy@3o- zD<~Hdz*eBLcOEVK+p&9dGpeg|sICFxUKTbNziRWDJ1F7NK{F?Yj|7a@7yhBvs9ho+ z7s3(rgf-gM(%ibf->88vD^c*C`&#z4xxKHxHNJmc<#=0NF(ArRfDqWoKBtkLF>vO^ zdwMjC9rvyt?OnPGp8bs|;6sxyh5L-dq9M0UKh&AbmWGESRA$bPqWGsGGA4>6P_aw1 zc6RO67q9J+Fm^Yt9p+N^EP=l8ENj4AdjG4TH%(1GjQo~c|Ck95S7a+Qiy2enY#iYD z)!OgYFYQq^h?8)QNwY6d87bFvwM8WUnStN(X^Ny!np$da|4xs9p=xgh5k*n7k;IQrmfo}GOzll2ZvoZxzk`tayWoX05k(w3ZOTDaR6!n zydWYkb{YutW|q+X9mZs=HD?0Y-fdxkI*#P&0`U~C>;lAm0N)4j-pn=t$N(77@m?f3 zM?@Nrls)^FFYnoQ*CP|eXzkD&r)@`fZo3*E|KX1N*Kx=FYd3TOVg$)D$u%9%*N{9& z@*R?Qko+3SCX#QG%$zK{-sc(rnEiybx0c|NAmt<=O&sE^mDBnd$@DRRabiR@z&Rrf z-Z4goG}c0l^fIc1L($O}<0!V}LP=wrv5=q?DcGC_u@)})vsDEQx}^)dMgoWd%oCBV z0Jf6c44{{YJkZ~&V+Jm4Qi%M4sg~ z3@o%JOkHR#l|!5c7afLSV+$7oG!U8)w!$LLB&pfb?I8Ue*hpa%p_jM91U z5O}~uR*lgt;oIVTocIuO0P+BLiYP^;uZsF4O?Mm7-*y4xc>sF=+(vSei13JIbS8iq z0AA|2I@_;?Bc%<(Iv<(3iUE1BoIsI9@E*1~MVvP%6&%9;GLXvuJ!8azdIInOuyN*U zKjt@+4jb^w_tqLnQ13j90(^Haj0jW!Akc-tR7F&csv0=>P$4P;iVY!%wYDsgi1Xf< z*tSO%Rt9~uzSAsIM2aM5bu5E>Is&Ed0MQG;BLF@PVDgcm4(kX~i^#&KZj%~&wW?-< zu{lvG8t26p^^nQiSlY@pK4EoI&{DS!v?NE8Y$4e|Qb{Ic(7ZaGXS7z;mV0Z4!4R$gKwO2unP~_LUI~_l>qhu7z2Kff>to6%Gug-k zvZ`Aj$LoGD#WQtJhq70nhOw{BUGjl;h{z)V&IRzPqk#Z`QZc#7r9>$EHAB8JeowcC z0RU3wEPu!seKFJ*Thp0G?Jx4Oh`fHZe$Rb<(e$_`H?DG2Ke%?ir$;1ebw8T^uVPF3 zaN%Hx(p2x;wqV8h6V>_bm5WCwMLdwL%1PCr)1S7(s-EdF^^CNjx3{&2eUi^c@yGKk zc0c-;3CCSSy?OcQ$}=jKpH`Qzj;r`)@jf3&nN)xn{U?Y%%nk=kKNE?tK$Ih z*7}!wJLi9f5UyhCKmk-tvl#PN72O14FCF(QEEdKD}%49 zKkk&hTm3}Zx#mSNMy7_~=aM+rs1Y>=cyHxXAVvU@cXO$787vmwRzU+j8wXted7jAl z-3rG0Y98x0)~U)Dtug1oW1J768iEL5tBvEGYR%@PRC*Fg`uM;vomV&+WBw0WZG3)j S_5(5i0000