diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts index 18e05ab38779..d811006f4500 100644 --- a/backend/__tests__/api/controllers/user.spec.ts +++ b/backend/__tests__/api/controllers/user.spec.ts @@ -51,6 +51,9 @@ describe("user controller test", () => { enabled: false, maxMail: 0, }, + premium: { + enabled: true, + }, }, } as any); diff --git a/backend/__tests__/dal/leaderboards.spec.ts b/backend/__tests__/dal/leaderboards.spec.ts index 56b7d5f4dcc2..ae3561ed346e 100644 --- a/backend/__tests__/dal/leaderboards.spec.ts +++ b/backend/__tests__/dal/leaderboards.spec.ts @@ -3,6 +3,8 @@ import { ObjectId } from "mongodb"; import * as UserDal from "../../src/dal/user"; import * as LeaderboardsDal from "../../src/dal/leaderboards"; import * as PublicDal from "../../src/dal/public"; +import * as Configuration from "../../src/init/configuration"; +const configuration = Configuration.getCachedConfiguration(); import * as DB from "../../src/init/db"; @@ -50,10 +52,10 @@ describe("LeaderboardsDal", () => { const lb = result.map((it) => _.omit(it, ["_id"])); expect(lb).toEqual([ - expectedLbEntry(1, rank1, "15"), - expectedLbEntry(2, rank2, "15"), - expectedLbEntry(3, rank3, "15"), - expectedLbEntry(4, rank4, "15"), + expectedLbEntry("15", { rank: 1, user: rank1 }), + expectedLbEntry("15", { rank: 2, user: rank2 }), + expectedLbEntry("15", { rank: 3, user: rank3 }), + expectedLbEntry("15", { rank: 4, user: rank4 }), ]); }); it("should create leaderboard time english 60", async () => { @@ -76,10 +78,10 @@ describe("LeaderboardsDal", () => { const lb = result.map((it) => _.omit(it, ["_id"])); expect(lb).toEqual([ - expectedLbEntry(1, rank1, "60"), - expectedLbEntry(2, rank2, "60"), - expectedLbEntry(3, rank3, "60"), - expectedLbEntry(4, rank4, "60"), + expectedLbEntry("60", { rank: 1, user: rank1 }), + expectedLbEntry("60", { rank: 2, user: rank2 }), + expectedLbEntry("60", { rank: 3, user: rank3 }), + expectedLbEntry("60", { rank: 4, user: rank4 }), ]); }); it("should not include discord properties for users without discord connection", async () => { @@ -154,10 +156,113 @@ describe("LeaderboardsDal", () => { //THEN expect(result).toEqual({ "20": 2, "110": 2 }); }); + + it("should create leaderboard with badges", async () => { + //GIVEN + const noBadge = await createUser(lbBests(pb(4))); + const oneBadgeSelected = await createUser(lbBests(pb(3)), { + inventory: { badges: [{ id: 1, selected: true }] }, + }); + const oneBadgeNotSelected = await createUser(lbBests(pb(2)), { + inventory: { badges: [{ id: 1, selected: false }] }, + }); + const multipleBadges = await createUser(lbBests(pb(1)), { + inventory: { + badges: [ + { id: 1, selected: false }, + { id: 2, selected: true }, + { id: 3, selected: true }, + ], + }, + }); + + //WHEN + await LeaderboardsDal.update("time", "15", "english"); + const result = (await LeaderboardsDal.get( + "time", + "15", + "english", + 0 + )) as SharedTypes.LeaderboardEntry[]; + + //THEN + const lb = result.map((it) => _.omit(it, ["_id"])); + + expect(lb).toEqual([ + expectedLbEntry("15", { rank: 1, user: noBadge }), + expectedLbEntry("15", { + rank: 2, + user: oneBadgeSelected, + badgeId: 1, + }), + expectedLbEntry("15", { rank: 3, user: oneBadgeNotSelected }), + expectedLbEntry("15", { + rank: 4, + user: multipleBadges, + badgeId: 2, + }), + ]); + }); + + it("should create leaderboard with premium", async () => { + await enablePremiumFeatures(true); + //GIVEN + const noPremium = await createUser(lbBests(pb(4))); + const lifetime = await createUser(lbBests(pb(3)), premium(-1)); + const validPremium = await createUser(lbBests(pb(2)), premium(10)); + const expiredPremium = await createUser(lbBests(pb(1)), premium(-10)); + + //WHEN + await LeaderboardsDal.update("time", "15", "english"); + const result = (await LeaderboardsDal.get( + "time", + "15", + "english", + 0 + )) as SharedTypes.LeaderboardEntry[]; + + //THEN + const lb = result.map((it) => _.omit(it, ["_id"])); + + expect(lb).toEqual([ + expectedLbEntry("15", { rank: 1, user: noPremium }), + expectedLbEntry("15", { + rank: 2, + user: lifetime, + isPremium: true, + }), + expectedLbEntry("15", { + rank: 3, + user: validPremium, + isPremium: true, + }), + expectedLbEntry("15", { rank: 4, user: expiredPremium }), + ]); + }); + it("should create leaderboard without premium if feature disabled", async () => { + await enablePremiumFeatures(false); + //GIVEN + const lifetime = await createUser(lbBests(pb(3)), premium(-1)); + + //WHEN + await LeaderboardsDal.update("time", "15", "english"); + const result = (await LeaderboardsDal.get( + "time", + "15", + "english", + 0 + )) as SharedTypes.LeaderboardEntry[]; + + //THEN + expect(result[0]?.isPremium).toBeUndefined(); + }); }); }); -function expectedLbEntry(rank: number, user: MonkeyTypes.DBUser, time: string) { +function expectedLbEntry( + time: string, + { rank, user, badgeId, isPremium }: ExpectedLbEntry +) { const lbBest: SharedTypes.PersonalBest = user.lbPersonalBests?.time[time].english; @@ -172,7 +277,8 @@ function expectedLbEntry(rank: number, user: MonkeyTypes.DBUser, time: string) { consistency: lbBest.consistency, discordId: user.discordId, discordAvatar: user.discordAvatar, - badgeId: 2, + badgeId, + isPremium, }; } @@ -192,12 +298,6 @@ async function createUser( timeTyping: 7200, discordId: "discord " + uid, discordAvatar: "avatar " + uid, - inventory: { - badges: [ - { id: 1, selected: false }, - { id: 2, selected: true }, - ], - }, ...userProperties, lbPersonalBests, }, @@ -234,3 +334,32 @@ function pb( timestamp, }; } + +function premium(expirationDeltaSeconds) { + return { + premium: { + startTimestamp: 0, + expirationTimestamp: + expirationDeltaSeconds === -1 + ? -1 + : Date.now() + expirationDeltaSeconds * 1000, + }, + }; +} + +interface ExpectedLbEntry { + rank: number; + user: MonkeyTypes.DBUser; + badgeId?: number; + isPremium?: boolean; +} + +async function enablePremiumFeatures(premium: boolean): Promise { + const mockConfig = _.merge(await configuration, { + users: { premium: { enabled: premium } }, + }); + + jest + .spyOn(Configuration, "getCachedConfiguration") + .mockResolvedValue(mockConfig); +} diff --git a/backend/src/api/controllers/leaderboard.ts b/backend/src/api/controllers/leaderboard.ts index 97f763a602cb..bed10e239727 100644 --- a/backend/src/api/controllers/leaderboard.ts +++ b/backend/src/api/controllers/leaderboard.ts @@ -105,7 +105,8 @@ export async function getDailyLeaderboard( const topResults = await dailyLeaderboard.getResults( minRank, maxRank, - req.ctx.configuration.dailyLeaderboards + req.ctx.configuration.dailyLeaderboards, + req.ctx.configuration.users.premium.enabled ); return new MonkeyResponse("Daily leaderboard retrieved", topResults); diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts index ac1f6811aea9..2b9026912ab3 100644 --- a/backend/src/api/controllers/result.ts +++ b/backend/src/api/controllers/result.ts @@ -489,6 +489,8 @@ export async function addResult( (isDevEnvironment() || (user.timeTyping ?? 0) > 7200); const selectedBadgeId = user.inventory?.badges?.find((b) => b.selected)?.id; + const isPremium = + (await UserDAL.checkIfUserIsPremium(user.uid, user)) || undefined; if (dailyLeaderboard && validResultCriteria) { incrementDailyLeaderboard( @@ -508,6 +510,7 @@ export async function addResult( discordAvatar: user.discordAvatar, discordId: user.discordId, badgeId: selectedBadgeId, + isPremium, }, dailyLeaderboardsConfig ); diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts index 54f5dba8a770..a0d37f8504e4 100644 --- a/backend/src/api/controllers/user.ts +++ b/backend/src/api/controllers/user.ts @@ -753,6 +753,7 @@ export async function getProfile( streak: streak?.length ?? 0, maxStreak: streak?.maxLength ?? 0, lbOptOut, + isPremium: await UserDAL.checkIfUserIsPremium(user.uid, user), }; if (banned) { diff --git a/backend/src/dal/leaderboards.ts b/backend/src/dal/leaderboards.ts index 8f4a9f383a9c..797110f1d99b 100644 --- a/backend/src/dal/leaderboards.ts +++ b/backend/src/dal/leaderboards.ts @@ -3,6 +3,7 @@ import Logger from "../utils/logger"; import { performance } from "perf_hooks"; import { setLeaderboard } from "../utils/prometheus"; import { isDevEnvironment } from "../utils/misc"; +import { getCachedConfiguration } from "../init/configuration"; const leaderboardUpdating: Record = {}; @@ -27,6 +28,13 @@ export async function get( .skip(skip) .limit(limit) .toArray(); + + const premiumFeaturesEnabled = (await getCachedConfiguration(true)).users + .premium.enabled; + + if (!premiumFeaturesEnabled) { + preset.forEach((it) => (it.isPremium = undefined)); + } return preset; } catch (e) { if (e.error === 175) { @@ -77,7 +85,6 @@ export async function update( const key = `lbPersonalBests.${mode}.${mode2}.${language}`; const lbCollectionName = `leaderboards.${language}.${mode}.${mode2}`; leaderboardUpdating[`${language}_${mode}_${mode2}`] = true; - const start1 = performance.now(); const lb = db .collection("users") .aggregate( @@ -127,41 +134,43 @@ export async function update( discordId: 1, discordAvatar: 1, inventory: 1, + premium: 1, }, }, { $addFields: { - [`${key}.uid`]: "$uid", - [`${key}.name`]: "$name", - [`${key}.discordId`]: { - $ifNull: ["$discordId", "$$REMOVE"], - }, - [`${key}.discordAvatar`]: { - $ifNull: ["$discordAvatar", "$$REMOVE"], - }, + "user.uid": "$uid", + "user.name": "$name", + "user.discordId": { $ifNull: ["$discordId", "$$REMOVE"] }, + "user.discordAvatar": { $ifNull: ["$discordAvatar", "$$REMOVE"] }, [`${key}.consistency`]: { $ifNull: [`$${key}.consistency`, "$$REMOVE"], }, - [`${key}.rank`]: { + calculated: { $function: { - body: "function() {try {row_number+= 1;} catch (e) {row_number= 1;}return row_number;}", - args: [], - lang: "js", - }, - }, - [`${key}.badgeId`]: { - $function: { - body: "function(badges) {if (!badges) return null; for(let i=0;icurrentTime) || undefined; + return {rank:row_number,badgeId, isPremium}; + }`, }, }, }, }, { - $replaceRoot: { - newRoot: `$${key}`, + $replaceWith: { + $mergeObjects: [`$${key}`, "$user", "$calculated"], }, }, { $out: lbCollectionName }, @@ -169,6 +178,7 @@ export async function update( { allowDiskUse: true } ); + const start1 = performance.now(); await lb.toArray(); const end1 = performance.now(); @@ -179,7 +189,6 @@ export async function update( const end2 = performance.now(); //update speedStats - const start3 = performance.now(); const boundaries = [...Array(32).keys()].map((it) => it * 10); const statsKey = `${language}_${mode}_${mode2}`; const src = await db.collection(lbCollectionName); @@ -218,6 +227,7 @@ export async function update( ], { allowDiskUse: true } ); + const start3 = performance.now(); await histogram.toArray(); const end3 = performance.now(); @@ -258,6 +268,7 @@ async function createIndex(key: string): Promise { discordId: 1, discordAvatar: 1, inventory: 1, + premium: 1, }; const partial = { partialFilterExpression: { @@ -275,4 +286,10 @@ async function createIndex(key: string): Promise { export async function createIndicies(): Promise { await createIndex("lbPersonalBests.time.15.english"); await createIndex("lbPersonalBests.time.60.english"); + + if (isDevEnvironment()) { + Logger.info("Updating leaderboards in dev mode..."); + await update("time", "15", "english"); + await update("time", "60", "english"); + } } diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts index d6715c4bdb9d..e7d38e767589 100644 --- a/backend/src/dal/user.ts +++ b/backend/src/dal/user.ts @@ -7,6 +7,7 @@ import MonkeyError from "../utils/error"; import { Collection, ObjectId, Long, UpdateFilter } from "mongodb"; import Logger from "../utils/logger"; import { flattenObjectDeep, isToday, isYesterday } from "../utils/misc"; +import { getCachedConfiguration } from "../init/configuration"; const SECONDS_PER_HOUR = 3600; @@ -1049,6 +1050,11 @@ export async function checkIfUserIsPremium( uid: string, userInfoOverride?: MonkeyTypes.DBUser ): Promise { + const premiumFeaturesEnabled = (await getCachedConfiguration(true)).users + .premium.enabled; + if (!premiumFeaturesEnabled) { + return false; + } const user = userInfoOverride ?? (await getUser(uid, "checkIfUserIsPremium")); const expirationDate = user.premium?.expirationTimestamp; diff --git a/backend/src/types/types.d.ts b/backend/src/types/types.d.ts index fe9874270546..b3763afdd499 100644 --- a/backend/src/types/types.d.ts +++ b/backend/src/types/types.d.ts @@ -20,7 +20,7 @@ declare namespace MonkeyTypes { type DBUser = Omit< SharedTypes.User, - "resultFilterPresets" | "tags" | "customThemes" + "resultFilterPresets" | "tags" | "customThemes" | "isPremium" > & { _id: ObjectId; resultFilterPresets?: WithObjectIdArray; diff --git a/backend/src/utils/daily-leaderboards.ts b/backend/src/utils/daily-leaderboards.ts index 2decfd777bff..25ce3af6183c 100644 --- a/backend/src/utils/daily-leaderboards.ts +++ b/backend/src/utils/daily-leaderboards.ts @@ -14,6 +14,7 @@ type DailyLeaderboardEntry = { discordAvatar?: string; discordId?: string; badgeId?: number; + isPremium?: boolean; }; type GetRankResponse = { @@ -123,7 +124,8 @@ export class DailyLeaderboard { public async getResults( minRank: number, maxRank: number, - dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"] + dailyLeaderboardsConfig: SharedTypes.Configuration["dailyLeaderboards"], + premiumFeaturesEnabled: boolean ): Promise { const connection = RedisClient.getConnection(); if (!connection || !dailyLeaderboardsConfig.enabled) { @@ -156,6 +158,10 @@ export class DailyLeaderboard { }) ); + if (!premiumFeaturesEnabled) { + resultsWithRanks.forEach((it) => (it.isPremium = undefined)); + } + return resultsWithRanks; } diff --git a/backend/src/workers/later-worker.ts b/backend/src/workers/later-worker.ts index 0ac87cc594a6..b9992c6ff4b8 100644 --- a/backend/src/workers/later-worker.ts +++ b/backend/src/workers/later-worker.ts @@ -31,7 +31,8 @@ async function handleDailyLeaderboardResults( const allResults = await dailyLeaderboard.getResults( 0, -1, - dailyLeaderboardsConfig + dailyLeaderboardsConfig, + false ); if (allResults.length === 0) { diff --git a/frontend/src/html/pages/account.html b/frontend/src/html/pages/account.html index 38d614f48962..4c572812f442 100644 --- a/frontend/src/html/pages/account.html +++ b/frontend/src/html/pages/account.html @@ -29,7 +29,10 @@
-
-
+
+
-
+
+
-
diff --git a/frontend/src/html/pages/profile.html b/frontend/src/html/pages/profile.html index 2a580c4ffdf9..e5de1f1cd1b4 100644 --- a/frontend/src/html/pages/profile.html +++ b/frontend/src/html/pages/profile.html @@ -27,7 +27,10 @@
-
-
+
+
-
+
+
-
diff --git a/frontend/src/styles/leaderboards.scss b/frontend/src/styles/leaderboards.scss index 54b6fc942fa5..d798e32f188f 100644 --- a/frontend/src/styles/leaderboards.scss +++ b/frontend/src/styles/leaderboards.scss @@ -159,6 +159,12 @@ .badge { font-size: 0.6rem; } + .flagsAndBadge { + display: flex; + gap: 0.5rem; + color: var(--sub-color); + place-items: center; + } } tr td:first-child { diff --git a/frontend/src/styles/nav.scss b/frontend/src/styles/nav.scss index 3c18c3338547..0f96702b0416 100644 --- a/frontend/src/styles/nav.scss +++ b/frontend/src/styles/nav.scss @@ -17,8 +17,11 @@ nav { .textButton { .text { - font-size: 0.65em; + font-size: 0.75em; align-self: center; + .fas { + margin-left: 0.33em; + } } .icon, @@ -34,7 +37,7 @@ nav { &.account { position: relative; align-items: center; - gap: 0.25em; + gap: 0.33em; display: grid; grid-auto-flow: column; .loading, @@ -107,8 +110,13 @@ nav { } } } - &:hover .level { - background-color: var(--text-color); + &:hover { + .level { + background-color: var(--text-color); + } + .userFlags { + color: var(--text-color); + } } } } diff --git a/frontend/src/styles/profile.scss b/frontend/src/styles/profile.scss index 9e33bbb51ff0..45e193d75c23 100644 --- a/frontend/src/styles/profile.scss +++ b/frontend/src/styles/profile.scss @@ -355,17 +355,20 @@ grid-column: 1/2; } } - .name { - // font-size: 3rem; - // line-height: 2.5rem; + .user { + display: flex; + flex-wrap: wrap; font-size: 1rem; - line-height: 100%; + // line-height: 100%; width: max-content; - .bannedIcon { - margin-left: 0.5em; - display: inline-grid; - font-size: 75%; - color: var(--error-color); + gap: 0.35rem; + .userFlags { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; + // font-size: 0.75em; + color: var(--sub-color); + place-items: center left; } } .badge { diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index 7ca488a82c58..6fa388f508ba 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -107,8 +107,14 @@ .ssWatermark { font-size: 1.25rem; color: var(--sub-color); - line-height: 1rem; - text-align: right; + display: flex; + justify-content: flex-end; + gap: 0 1em; + flex-wrap: wrap; + + .fas { + margin-left: 0.33em; + } } #timerNumber { diff --git a/frontend/src/ts/controllers/account-controller.ts b/frontend/src/ts/controllers/account-controller.ts index def05909366b..263de919f6d9 100644 --- a/frontend/src/ts/controllers/account-controller.ts +++ b/frontend/src/ts/controllers/account-controller.ts @@ -40,6 +40,7 @@ import { } from "../test/test-config"; import * as ConnectionState from "../states/connection"; import { navigate } from "./route-controller"; +import { getHtmlByUserFlags } from "./user-flag-controller"; let signedOutThisSession = false; @@ -114,6 +115,9 @@ async function getDataAndInit(): Promise { LoadingPage.updateText("Applying settings..."); const snapshot = DB.getSnapshot() as MonkeyTypes.Snapshot; $("nav .textButton.account > .text").text(snapshot.name); + $("nav .textButton.account > .text").append( + getHtmlByUserFlags(snapshot, { iconsOnly: true }) + ); showFavoriteQuoteLength(); ResultFilters.loadTags(snapshot.tags); diff --git a/frontend/src/ts/controllers/user-flag-controller.ts b/frontend/src/ts/controllers/user-flag-controller.ts new file mode 100644 index 000000000000..1c044652687a --- /dev/null +++ b/frontend/src/ts/controllers/user-flag-controller.ts @@ -0,0 +1,83 @@ +const flags: UserFlag[] = [ + { + name: "Prime Ape", + description: "Paying for a monthly subscription", + icon: "fa-dollar-sign", + test: (it) => it.isPremium === true, + }, + { + name: "Banned", + description: "This account is banned", + icon: "fa-gavel", + color: "var(--error-color)", + test: (it) => it.banned === true, + }, + { + name: "LbOptOut", + description: "This account has opted out of leaderboards", + icon: "fa-crown", + color: "var(--error-color)", + test: (it) => it.lbOptOut === true, + }, +]; + +type SupportsFlags = { + isPremium?: boolean; + banned?: boolean; + lbOptOut?: boolean; +}; + +type UserFlag = { + readonly name: string; + readonly description: string; + readonly icon: string; + readonly color?: string; + readonly background?: string; + readonly customStyle?: string; + test(source: SupportsFlags): boolean; +}; + +type UserFlagOptions = { + iconsOnly?: boolean; +}; + +const USER_FLAG_OPTIONS_DEFAULT: UserFlagOptions = { + iconsOnly: false, +}; + +function getMatchingFlags(source: SupportsFlags): UserFlag[] { + const result = flags.filter((it) => it.test(source)); + return result; +} +function toHtml(flag: UserFlag, formatOptions: UserFlagOptions): string { + const icon = ``; + + if (formatOptions.iconsOnly) { + return icon; + } + + const style = []; + if (flag.background !== undefined) { + style.push(`background: ${flag.background};`); + } + if (flag?.color !== undefined) { + style.push(`color: ${flag.color};`); + } + if (flag?.customStyle !== undefined) { + style.push(flag.customStyle); + } + + const balloon = `aria-label="${flag.description}" data-balloon-pos="right"`; + + return `
${icon}
`; +} + +export function getHtmlByUserFlags( + source: SupportsFlags, + options?: UserFlagOptions +): string { + const formatOptions = { ...USER_FLAG_OPTIONS_DEFAULT, ...options }; + return getMatchingFlags(source) + .map((it) => toHtml(it, formatOptions)) + .join(""); +} diff --git a/frontend/src/ts/elements/leaderboards.ts b/frontend/src/ts/elements/leaderboards.ts index ba4d59ac8e79..4d04070b6d58 100644 --- a/frontend/src/ts/elements/leaderboards.ts +++ b/frontend/src/ts/elements/leaderboards.ts @@ -12,6 +12,7 @@ import * as Skeleton from "../utils/skeleton"; import { debounce } from "throttle-debounce"; import Format from "../utils/format"; import SlimSelect from "slim-select"; +import { getHtmlByUserFlags } from "../controllers/user-flag-controller"; const wrapperId = "leaderboardsWrapper"; @@ -336,7 +337,10 @@ async function fillTable(lb: LbKey): Promise { ${entry.name} - ${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""} +
+ ${getHtmlByUserFlags(entry)} + ${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""} +
${Format.typingSpeed(entry.wpm, { diff --git a/frontend/src/ts/elements/profile.ts b/frontend/src/ts/elements/profile.ts index dbb4db76b233..6763241a0f88 100644 --- a/frontend/src/ts/elements/profile.ts +++ b/frontend/src/ts/elements/profile.ts @@ -7,6 +7,7 @@ import { throttle } from "throttle-debounce"; import * as EditProfilePopup from "../popups/edit-profile-popup"; import * as ActivePage from "../states/active-page"; import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict"; +import { getHtmlByUserFlags } from "../controllers/user-flag-controller"; type ProfileViewPaths = "profile" | "account"; type UserProfileOrSnapshot = SharedTypes.UserProfile | MonkeyTypes.Snapshot; @@ -23,7 +24,6 @@ export async function update( profileElement.attr("uid", profile.uid ?? ""); profileElement.attr("name", profile.name ?? ""); - profileElement.attr("lbOptOut", `${profile.lbOptOut ?? false}`); // ============================================================================ // DO FREAKING NOT USE .HTML OR .APPEND HERE - USER INPUT!!!!!! @@ -31,8 +31,6 @@ export async function update( const banned = profile.banned === true; - const lbOptOut = profile.lbOptOut === true; - if ( details === undefined || profile === undefined || @@ -78,22 +76,9 @@ export async function update( } details.find(".name").text(profile.name); + details.find(".userFlags").html(getHtmlByUserFlags(profile)); - if (banned) { - details - .find(".name") - .append( - `
` - ); - } - - if (lbOptOut) { - details - .find(".name") - .append( - `
` - ); - + if (profile.lbOptOut === true) { if (where === "profile") { profileElement .find(".lbOptOutReminder") @@ -413,7 +398,7 @@ export function updateNameFontSize(where: ProfileViewPaths): void { details = $(".pageProfile .profile .details"); } if (!details) return; - const nameFieldjQ = details.find(".name"); + const nameFieldjQ = details.find(".user"); const nameFieldParent = nameFieldjQ.parent()[0]; const nameField = nameFieldjQ[0]; const upperLimit = Misc.convertRemToPixels(2); diff --git a/frontend/src/ts/pages/profile.ts b/frontend/src/ts/pages/profile.ts index 5ae753b344ab..f0cf3f18ecea 100644 --- a/frontend/src/ts/pages/profile.ts +++ b/frontend/src/ts/pages/profile.ts @@ -19,7 +19,10 @@ function reset(): void {
-
-
+
+
-
+
+
-
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 29caef657f6a..152ef5720b79 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -23,6 +23,7 @@ import * as ResultWordHighlight from "../elements/result-word-highlight"; import * as ActivePage from "../states/active-page"; import Format from "../utils/format"; import * as Loader from "../elements/loader"; +import { getHtmlByUserFlags } from "../controllers/user-flag-controller"; async function gethtml2canvas(): Promise { return (await import("html2canvas")).default; @@ -449,17 +450,20 @@ export async function screenshot(): Promise { const dateNow = new Date(Date.now()); $("#resultReplay").addClass("hidden"); $(".pageTest .ssWatermark").removeClass("hidden"); - $(".pageTest .ssWatermark").text( - format(dateNow, "dd MMM yyyy HH:mm") + " | monkeytype.com " - ); - if (isAuthenticated()) { - $(".pageTest .ssWatermark").text( - DB.getSnapshot()?.name + - " | " + - format(dateNow, "dd MMM yyyy HH:mm") + - " | monkeytype.com " - ); + + const snapshot = DB.getSnapshot(); + const ssWatermark = [format(dateNow, "dd MMM yyyy HH:mm"), "monkeytype.com"]; + if (snapshot?.name !== undefined) { + const userText = `${snapshot?.name}${getHtmlByUserFlags(snapshot, { + iconsOnly: true, + })}`; + ssWatermark.unshift(userText); } + $(".pageTest .ssWatermark").html( + ssWatermark + .map((el) => `${el}`) + .join("|") + ); $(".pageTest .buttons").addClass("hidden"); $("#notificationCenter").addClass("hidden"); $("#commandLineMobileButton").addClass("hidden"); diff --git a/shared-types/types.d.ts b/shared-types/types.d.ts index c306a09b4499..5d8ebb6ad79d 100644 --- a/shared-types/types.d.ts +++ b/shared-types/types.d.ts @@ -446,7 +446,8 @@ declare namespace SharedTypes { discordId?: string; discordAvatar?: string; rank: number; - badgeId: number | null; + badgeId?: number; + isPremium?: boolean; } type PostResultResponse = { @@ -526,6 +527,7 @@ declare namespace SharedTypes { profileDetails?: UserProfileDetails; customThemes?: CustomTheme[]; premium?: PremiumInfo; + isPremium?: boolean; quoteRatings?: UserQuoteRatings; favoriteQuotes?: Record; lbMemory?: UserLbMemory; @@ -575,6 +577,7 @@ declare namespace SharedTypes { | "lbOptOut" | "inventory" | "uid" + | "isPremium" > & { typingStats: { completedTests: User["completedTests"];