mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:20:43 +00:00
Add glowing red dot on store when skins change (#3066)
## Description: Hash the pattern + is for sale and store it in local storage. then check if the new cosmetics hash is different from the one we last saw. If it's different, add a glowing red dot on the store. After user clicks it, then update the hash and remove the dot. <img width="1030" height="143" alt="Screenshot 2026-01-29 at 3 54 46 PM" src="https://github.com/user-attachments/assets/e5727764-40e4-45e1-b651-65816e657067" /> ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -30,6 +30,18 @@ export async function handlePurchase(
|
||||
}
|
||||
|
||||
let __cosmetics: Promise<Cosmetics | null> | null = null;
|
||||
let __cosmeticsHash: string | null = null;
|
||||
|
||||
function simpleHash(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return hash.toString(36);
|
||||
}
|
||||
|
||||
export async function fetchCosmetics(): Promise<Cosmetics | null> {
|
||||
if (__cosmetics !== null) {
|
||||
return __cosmetics;
|
||||
@@ -46,6 +58,11 @@ export async function fetchCosmetics(): Promise<Cosmetics | null> {
|
||||
console.error(`Invalid cosmetics: ${result.error.message}`);
|
||||
return null;
|
||||
}
|
||||
const patternKeys = Object.keys(result.data.patterns).sort();
|
||||
const hashInput = patternKeys
|
||||
.map((k) => k + (result.data.patterns[k].product ? "sale" : ""))
|
||||
.join(",");
|
||||
__cosmeticsHash = simpleHash(hashInput);
|
||||
return result.data;
|
||||
} catch (error) {
|
||||
console.error("Error getting cosmetics:", error);
|
||||
@@ -55,6 +72,11 @@ export async function fetchCosmetics(): Promise<Cosmetics | null> {
|
||||
return __cosmetics;
|
||||
}
|
||||
|
||||
export async function getCosmeticsHash(): Promise<string | null> {
|
||||
await fetchCosmetics();
|
||||
return __cosmeticsHash;
|
||||
}
|
||||
|
||||
export function patternRelationship(
|
||||
pattern: Pattern,
|
||||
colorPalette: { name: string; isArchived?: boolean } | null,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { getCosmeticsHash } from "../Cosmetics";
|
||||
import { getGamesPlayed } from "../Utils";
|
||||
|
||||
const HELP_SEEN_KEY = "helpSeen";
|
||||
const STORE_SEEN_HASH_KEY = "storeSeenHash";
|
||||
|
||||
@customElement("desktop-nav-bar")
|
||||
export class DesktopNavBar extends LitElement {
|
||||
@state() private _helpSeen = localStorage.getItem(HELP_SEEN_KEY) === "true";
|
||||
@state() private _hasNewCosmetics = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
@@ -23,6 +26,12 @@ export class DesktopNavBar extends LitElement {
|
||||
this._updateActiveState(current);
|
||||
});
|
||||
}
|
||||
|
||||
// Check if cosmetics have changed
|
||||
getCosmeticsHash().then((hash: string | null) => {
|
||||
const seenHash = localStorage.getItem(STORE_SEEN_HASH_KEY);
|
||||
this._hasNewCosmetics = hash !== null && hash !== seenHash;
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -46,14 +55,29 @@ export class DesktopNavBar extends LitElement {
|
||||
}
|
||||
|
||||
private showHelpDot(): boolean {
|
||||
// Only show one dot at a time to prevent
|
||||
// overwhelming users.
|
||||
return getGamesPlayed() < 10 && !this._helpSeen;
|
||||
}
|
||||
|
||||
private showStoreDot(): boolean {
|
||||
return this._hasNewCosmetics && !this.showHelpDot();
|
||||
}
|
||||
|
||||
private onHelpClick = () => {
|
||||
localStorage.setItem(HELP_SEEN_KEY, "true");
|
||||
this._helpSeen = true;
|
||||
};
|
||||
|
||||
private onStoreClick = () => {
|
||||
this._hasNewCosmetics = false;
|
||||
getCosmeticsHash().then((hash: string | null) => {
|
||||
if (hash !== null) {
|
||||
localStorage.setItem(STORE_SEEN_HASH_KEY, hash);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<nav
|
||||
@@ -123,11 +147,18 @@ export class DesktopNavBar extends LitElement {
|
||||
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
|
||||
data-page="page-item-store"
|
||||
data-i18n="main.store"
|
||||
@click=${this.onStoreClick}
|
||||
></button>
|
||||
<span
|
||||
class="absolute -top-3 -right-2 bg-gradient-to-br from-red-600 to-red-700 text-white text-[9px] font-black tracking-wider px-2 py-0.5 rounded rotate-12 shadow-lg shadow-red-600/50 animate-pulse pointer-events-none"
|
||||
data-i18n="main.store_new_badge"
|
||||
></span>
|
||||
${this.showStoreDot()
|
||||
? html`
|
||||
<span
|
||||
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full animate-ping"
|
||||
></span>
|
||||
<span
|
||||
class="absolute -top-1 -right-1 w-2 h-2 bg-red-500 rounded-full"
|
||||
></span>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<button
|
||||
class="nav-menu-item text-white/70 hover:text-blue-500 font-bold tracking-widest uppercase cursor-pointer transition-colors [&.active]:text-blue-500"
|
||||
|
||||
Reference in New Issue
Block a user