-
Notifications
You must be signed in to change notification settings - Fork 0
[PROD RELEASE] - Jan 26 #404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5ce87bb
feb030b
88c8d5a
2fac1be
8bfea14
7319496
77879c2
39a09f0
85d7442
691e47e
eedccab
ed41590
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,8 +5,10 @@ | |
| let className = ''; | ||
| export { className as class }; | ||
| export let yOffset = 0; | ||
| export let delayYOffset = 0; | ||
|
|
||
| let elRef: HTMLElement | undefined; | ||
| let placeholderRef: HTMLElement | undefined; | ||
| let elYOffset = 0; | ||
|
|
||
| function handleScroll() { | ||
|
|
@@ -15,7 +17,10 @@ | |
| } | ||
| const { scrollY } = window; | ||
|
|
||
| const isFixed = (scrollY + yOffset - elYOffset) >= 0; | ||
| const isFixed = (scrollY + yOffset - elYOffset - delayYOffset) >= 0; | ||
| if (placeholderRef) { | ||
| Object.assign(placeholderRef.style, {height: isFixed ? `${elRef.offsetHeight}px` : 0}); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
| elRef.classList.toggle(styles.sticky, isFixed); | ||
| } | ||
|
|
||
|
|
@@ -31,6 +36,7 @@ | |
| }) | ||
| </script> | ||
|
|
||
| <div bind:this={placeholderRef}></div> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| <div bind:this={elRef} class={className} style="--top-offset: {yOffset}px"> | ||
| <slot /> | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,31 +39,20 @@ | |
|
|
||
| debounce = user.handle; | ||
|
|
||
| if (!DISABLE_NUDGES) { | ||
| const completednessData = await fetchUserProfileCompletedness(user, true); | ||
| if (!completednessData) { | ||
| return; | ||
| } | ||
| $ctx.auth = { | ||
| ...$ctx.auth, | ||
| profileCompletionData: { | ||
| completed: completednessData.data?.percentComplete === 100, | ||
| handle: completednessData.handle, | ||
| percentComplete: completednessData.data?.percentComplete, | ||
| showToast: completednessData.showToast, | ||
| }, | ||
| }; | ||
| } else { | ||
| $ctx.auth = { | ||
| ...$ctx.auth, | ||
| profileCompletionData: { | ||
| completed: true, | ||
| handle: user?.handle, | ||
| percentComplete: 0, | ||
| showToast: "", | ||
| }, | ||
| }; | ||
| const completednessData = await fetchUserProfileCompletedness(user, true); | ||
| if (!completednessData) { | ||
| return; | ||
| } | ||
| $ctx.auth = { | ||
| ...$ctx.auth, | ||
| profileCompletionData: { | ||
| completed: completednessData.data?.percentComplete === 100, | ||
| handle: completednessData.handle, | ||
| percentComplete: completednessData.data?.percentComplete, | ||
| showToast: DISABLE_NUDGES ? '' : completednessData.showToast, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| dateFields: completednessData.data?.dateFields, | ||
| }, | ||
| }; | ||
|
|
||
| setTimeout(() => debounce = '', 100); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,17 +4,18 @@ import { DISABLE_NUDGES, NUDGES_DISABLED_HOSTS } from "lib/config/profile-toasts | |
|
|
||
| import { getRequestAuthHeaders } from "./auth-jwt"; | ||
|
|
||
| export function isOnHost(host: string): boolean { | ||
| const locationHostname = window?.location.hostname ?? '' | ||
| return !!host.match(new RegExp(`^https?:\/\/${locationHostname}`, 'i')); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| } | ||
|
|
||
| /** | ||
| * Check if we're on a domain that should not show the profile nudges | ||
| * @returns Boolean | ||
| */ | ||
| export function dismissNudgesBasedOnHost(): boolean { | ||
| export function dismissNudgesBasedOnHost(exceptHost?: string): boolean { | ||
| // Ue the new flag to disable the profile nudges completely (PS-267) | ||
| const locationHostname = window?.location.hostname ?? '' | ||
| return DISABLE_NUDGES || | ||
| (!!NUDGES_DISABLED_HOSTS.find(host => ( | ||
| host.match(new RegExp(`^https?:\/\/${locationHostname}`, 'i')) | ||
| ))); | ||
| return DISABLE_NUDGES || NUDGES_DISABLED_HOSTS.filter(h => !exceptHost || h !== exceptHost).some(isOnHost); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| // store fetched data in a local cache (de-duplicate immediate api calls) | ||
|
|
@@ -25,7 +26,7 @@ export interface ProfileCompletednessResponse { | |
| showToast: string | ||
| data: { | ||
| percentComplete: number | ||
| } | ||
| } & {[key: string]: any} | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -55,13 +56,29 @@ export const fetchUserProfileCompletedness = async (user: AuthUser, force = fals | |
| const request = fetch(requestUrl, {headers: {...getRequestAuthHeaders()}}); | ||
|
|
||
| const response = await (await request).json(); | ||
|
|
||
| const responseData = response.data ?? {}; | ||
| const dateFields = Object.keys(responseData) | ||
| .filter(k => k.endsWith('LastUpdateDate') || k === 'lastProfileConfirmationDate') | ||
| .map((key) => [key, new Date(responseData[key])]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
|
|
||
| resolve({ | ||
| ...response, | ||
| data: { | ||
| ...response.data, | ||
| ...responseData, | ||
| dateFields, | ||
| percentComplete: (response?.data?.percentComplete ?? 0) * 100, | ||
| }, | ||
| }); | ||
|
|
||
| return localCache[cacheKey]; | ||
| } | ||
|
|
||
| export const confirmProfileData = async (userHandle: string) => { | ||
| const requestUrl: string = `${TC_API_HOST}/members/${userHandle}/confirmProfile`; | ||
| const request = fetch(requestUrl, {method: 'POST', headers: {...getRequestAuthHeaders()}}); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
|
|
||
| const response = await (await request).json(); | ||
|
|
||
| return response.data; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| @use 'lib/styles/mixins.scss' as *; | ||
|
|
||
| .bannerOuter, | ||
| .nudgeOuter { | ||
| position: absolute; | ||
| top: calc(var(--uninav-header-height) + var(--top-offset)); | ||
|
|
@@ -13,6 +14,12 @@ | |
| } | ||
| } | ||
|
|
||
| .bannerOuter { | ||
| position: relative; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| top: 0; | ||
| height: min-content; | ||
| } | ||
|
|
||
| .nudgeWrap { | ||
| position: absolute; | ||
| display: flex; | ||
|
|
@@ -41,3 +48,26 @@ | |
| width: 100%; | ||
| } | ||
| } | ||
|
|
||
| .bannerWrap { | ||
| display: flex; | ||
| width: 100%; | ||
| background: #60267d; | ||
| color: #fff; | ||
| } | ||
|
|
||
| .bannerInner { | ||
| width: 100%; | ||
| margin: 0 auto; | ||
| @include maxViewWidth; | ||
| padding: 6px 32px; | ||
|
|
||
| @include tablet { | ||
| padding: 6px 16px; | ||
| } | ||
|
|
||
| @include mobile { | ||
| padding: 0 2px; | ||
| width: 100%; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,12 @@ | |
| import { getAppContext } from 'lib/app-context'; | ||
| import Toastr from './components/Toastr.svelte'; | ||
| import styles from './Nudge.module.scss'; | ||
| import type { ToastType } from 'lib/config/profile-toasts.config'; | ||
| import { toastsMeta, type ToastType } from 'lib/config/profile-toasts.config'; | ||
| import { getToast, hideToast } from './toast-manager'; | ||
| import Sticky from 'lib/components/sticky/Sticky.svelte'; | ||
| import type { ProfileCompletionData } from 'lib/app-context/profile-completion.model'; | ||
| import { getBanner, hide as hideBanner } from './banner-manager'; | ||
| import Banner from './components/Banner.svelte'; | ||
|
|
||
| const ctx = getAppContext(); | ||
|
|
||
|
|
@@ -16,20 +18,41 @@ | |
| } = $ctx.auth) | ||
|
|
||
| let toast: ToastType | undefined; | ||
| let banner: {key: string, date: Date} | undefined; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
|
|
||
| function dismissToast() { | ||
| toast = undefined; | ||
| hideToast(); | ||
| } | ||
|
|
||
| function dismissBanner() { | ||
| banner = undefined; | ||
| hideBanner(); | ||
| } | ||
|
|
||
| $: toast = isReady ? getToast(profileCompletionData as ProfileCompletionData) : undefined; | ||
| $: banner = isReady ? getBanner(profileCompletionData as ProfileCompletionData) : undefined; | ||
| </script> | ||
|
|
||
| {#if banner} | ||
| <Sticky class={styles.bannerOuter} delayYOffset={92}> | ||
| <div class={styles.bannerWrap}> | ||
| <div class={styles.bannerInner}> | ||
| <Banner | ||
| userHandle={user?.handle ?? ''} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| banner={banner} | ||
| on:dismiss={dismissBanner} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </Sticky> | ||
| {/if} | ||
|
|
||
| {#if toast} | ||
| <Sticky class={styles.nudgeOuter} yOffset={10}> | ||
| <div class={styles.nudgeWrap}> | ||
| <div class={styles.nudgeInner}> | ||
| <Toastr userhandle={user.handle} toast={toast} on:dismiss={dismissToast} /> | ||
| <Toastr userhandle={user?.handle ?? ''} toast={toast} on:dismiss={dismissToast} /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| </div> | ||
| </div> | ||
| </Sticky> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| import type { ProfileCompletionData } from 'lib/app-context/profile-completion.model'; | ||
| import { checkCookie, getCookieValue, setCookie } from '../utils/cookies'; | ||
| import { dismissNudgesBasedOnHost } from '../functions/profile-nudges'; | ||
| import { PROFILE_HOST } from 'lib/config'; | ||
|
|
||
| const COOKIE_NAME = 'uni-profilereminder-banner-shown'; | ||
| const COOKIE_ACTIVE_PERIOD_DAYS = 3; | ||
| const PROFILE_UPDATE_REMINDER_PERIOD_DAYS = 3*30; | ||
|
|
||
| const DAY = 24 * 3600 * 1000; | ||
| const _30DAYS = 30 * DAY; | ||
|
|
||
| function isDismissed() { | ||
| return checkCookie(COOKIE_NAME, 'hidden'); | ||
| } | ||
|
|
||
| function getLastSeen() { | ||
| return getCookieValue(COOKIE_NAME); | ||
| } | ||
|
|
||
| const isOlderThanTreshold = (date: Date | number, treshold: number): boolean => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| const diffInDays = Math.floor((Date.now() - +date) / DAY); | ||
| return diffInDays >= treshold; | ||
| } | ||
|
|
||
| /** | ||
| * @param completednessData | ||
| * @returns | ||
| */ | ||
| export const getBanner = (completednessData: ProfileCompletionData) => { | ||
| if (dismissNudgesBasedOnHost(PROFILE_HOST) || !completednessData || isDismissed()) { | ||
| return; | ||
| } | ||
|
|
||
| const fields = completednessData.dateFields ?? []; | ||
| const updatableProfileFields: [string, Date][] = fields | ||
| .filter(([k]) => k.endsWith('LastUpdateDate')) | ||
| .map(([k, d]) => [k.replace(/LastUpdateDate$/, ''), d]); | ||
| const lastProfileConfirmationDate = fields.find(([k]) => k === 'lastProfileConfirmationDate')?.[1]; | ||
|
|
||
| const sorted = updatableProfileFields | ||
| .sort((a, b) => +a[1] - +b[1]); | ||
|
|
||
|
|
||
| const lastUpdate = +sorted[sorted.length - 1][1]; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| if (!lastUpdate) { | ||
| return; | ||
| } | ||
|
|
||
| const lastUpdateOrCofirmDate = lastProfileConfirmationDate ? Math.max(lastUpdate, +lastProfileConfirmationDate) : lastUpdate; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| if (!lastUpdateOrCofirmDate || !isOlderThanTreshold(lastUpdateOrCofirmDate, PROFILE_UPDATE_REMINDER_PERIOD_DAYS)) { | ||
| setCookie(COOKIE_NAME, '', 0); | ||
| return; | ||
| } | ||
|
|
||
| let lastSeen = getLastSeen(); | ||
| if (!lastSeen) { | ||
| const oldFields = updatableProfileFields.filter(([,d]) => isOlderThanTreshold(d, PROFILE_UPDATE_REMINDER_PERIOD_DAYS)).map(([k]) => k); | ||
| const fieldKey = oldFields.length ? oldFields[0] : undefined; | ||
| const field = updatableProfileFields.find(f => f[0] === fieldKey) ?? []; | ||
| lastSeen = JSON.stringify({key: field[0], date: field[1]}); | ||
| if(lastSeen) { | ||
| setCookie(COOKIE_NAME, lastSeen, COOKIE_ACTIVE_PERIOD_DAYS); | ||
| } | ||
| } | ||
|
|
||
| return JSON.parse(lastSeen); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| } | ||
|
|
||
| export const hide = () => { | ||
| setCookie(COOKIE_NAME, 'hidden', COOKIE_ACTIVE_PERIOD_DAYS); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[⚠️
maintainability]The type
dateFields?: [string, Date][]suggests an array of tuples, each containing astringand aDate. Consider using a more descriptive type alias or interface for the tuple to improve readability and maintainability, especially if this structure is used in multiple places.