Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ workflows:
- TOP-2044_show-signin-modal
- maintenance
- review-app
- re-enable-nudges

- deployProd:
context: org-global
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"types": "./types/src/main.d.ts",
"repository": "https://github.com/topcoder-platform/universal-navigation.git",
"scripts": {
"build": "vite build --mode=production && npx rimraf ./types && npm run types",
"build:nudge": "APP_BUILD_TARGET=nudge vite build --mode=production",
"build:base": "vite build --mode=production && npx rimraf ./types && npm run types",
"build": "npm run build:base && npm run build:nudge",
"check": "svelte-check --tsconfig ./tsconfig.json",
"demo:dist": "npx http-server --cors --host local.topcoder-dev.com --port 8083 ./dist",
"demo:marketing": "npx http-server --host local.topcoder-dev.com --port 8081 ./demo/marketing -P http://local.topcoder-dev.com:8081? -o /",
Expand Down
35 changes: 26 additions & 9 deletions src/lib/components/user-area/UserArea.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { getAppContext } from 'lib/app-context';
import { checkUserAppRole, fetchUserProfile } from 'lib/functions/user-profile.provider';
import { fetchUserProfileCompletedness } from 'lib/functions/profile-nudges';
import { AUTH0_AUTHENTICATOR_URL } from 'lib/config';
import { AUTH_USER_ROLE } from 'lib/config/auth';
import { DISABLE_NUDGES } from "lib/config/profile-toasts.config";
Expand Down Expand Up @@ -38,15 +39,31 @@

debounce = user.handle;

$ctx.auth = {
...$ctx.auth,
profileCompletionData: {
completed: true,
handle: user?.handle,
percentComplete: 0,
showToast: "",
},
};
if (!DISABLE_NUDGES) {
const completednessData = await fetchUserProfileCompletedness(user, true);
Comment thread
vas3a marked this conversation as resolved.
if (!completednessData) {
Comment thread
vas3a marked this conversation as resolved.
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: "",
},
};
}

setTimeout(() => debounce = '', 100);
Comment thread
vas3a marked this conversation as resolved.
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/config/profile-toasts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const CUSTOMER_HOSTS = [
WORK_MANAGER_HOST,
]

export const DISABLE_NUDGES = true;
export const DISABLE_NUDGES = false;

export const NUDGES_DISABLED_HOSTS = [
...CUSTOMER_HOSTS,
Expand Down
12 changes: 12 additions & 0 deletions src/lib/functions/load-nudge-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const modulePath = BUILD_IS_PROD ? './nudge.js' : '../nudge-app/Nudge.svelte';

const loadModule = () => {
return import(/* @vite-ignore */ modulePath).then(d => d.default)
}

export const loadNudgeApp = async (ctx: any, targetEl: Element): Promise<void> => {
const NudgeApp = await loadModule();

// instantiate the nudge app
new NudgeApp({ target: targetEl, context: ctx });
}
67 changes: 67 additions & 0 deletions src/lib/functions/profile-nudges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { TC_API_HOST } from "lib/config";
import type { AuthUser } from "lib/app-context";
import { DISABLE_NUDGES, NUDGES_DISABLED_HOSTS } from "lib/config/profile-toasts.config";

import { getRequestAuthHeaders } from "./auth-jwt";

/**
* Check if we're on a domain that should not show the profile nudges
* @returns Boolean
*/
export function dismissNudgesBasedOnHost(): 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'))
)));
}

// store fetched data in a local cache (de-duplicate immediate api calls)
const localCache: Record<string, Promise<ProfileCompletednessResponse> | undefined> = {};

export interface ProfileCompletednessResponse {
handle: string
showToast: string
data: {
percentComplete: number
}
}

/**
* Fetches the user profile completedness
* @returns Promise<ProfileCompletednessResponse>
*/
export const fetchUserProfileCompletedness = async (user: AuthUser, force = false): Promise<ProfileCompletednessResponse | undefined> => {
const userHandle = user?.handle;

if (!userHandle) {
return undefined;
}

const cacheKey = `${userHandle}-completedness`;
if (!force && localCache[cacheKey]) {
return await localCache[cacheKey];
}


let resolve!: (value: ProfileCompletednessResponse) => void;
localCache[cacheKey] = new Promise<ProfileCompletednessResponse>((r) => { resolve = r });

// for QA purpose only
const toastOverrideFlagParam = (window?.location.search.match(/[?&]+toast=(\w+)/i) ?? [])[1];
const toastOverrideFlag = toastOverrideFlagParam ? `?toast=${toastOverrideFlagParam}` : '';
const requestUrl: string = `${TC_API_HOST}/members/${userHandle}/profileCompleteness${toastOverrideFlag}`;
const request = fetch(requestUrl, {headers: {...getRequestAuthHeaders()}});

const response = await (await request).json();
resolve({
...response,
data: {
...response.data,
percentComplete: (response?.data?.percentComplete ?? 0) * 100,
},
});

return localCache[cacheKey];
}
43 changes: 43 additions & 0 deletions src/lib/nudge-app/Nudge.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@use 'lib/styles/mixins.scss' as *;

.nudgeOuter {
position: absolute;
top: calc(var(--uninav-header-height) + var(--top-offset));
left: 0;
z-index: 19;

height: 0;
width: 100%;
@include mobile {
--top-offset: 2px!important;
}
}

.nudgeWrap {
position: absolute;
display: flex;
width: 100%;
margin: 0 auto;
@include maxViewWidth;
left: 50%;
transform: translateX(-50%);
}

.nudgeInner {
position: absolute;
right: 0;
display: flex;
flex-direction: column;
align-items: flex-end;

padding: 8px 32px;

@include tablet {
padding: 8px 16px;
}

@include mobile {
padding: 0 2px;
width: 100%;
}
}
36 changes: 36 additions & 0 deletions src/lib/nudge-app/Nudge.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts">
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 { getToast, hideToast } from './toast-manager';
import Sticky from 'lib/components/sticky/Sticky.svelte';
import type { ProfileCompletionData } from 'lib/app-context/profile-completion.model';

const ctx = getAppContext();

$: ({
ready: isReady,
user,
profileCompletionData,
} = $ctx.auth)

let toast: ToastType | undefined;

function dismissToast() {
toast = undefined;
hideToast();
}

$: toast = isReady ? getToast(profileCompletionData as ProfileCompletionData) : undefined;
</script>

{#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} />
</div>
</div>
</Sticky>
{/if}
12 changes: 12 additions & 0 deletions src/lib/nudge-app/components/Animation.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.animation {
width: 100%;
height: 100%;

svg {
transform: none!important;
}

img {
object-fit: contain;
}
}
53 changes: 53 additions & 0 deletions src/lib/nudge-app/components/Animation.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { getPublicPath } from 'lib/utils/paths';
import styles from './Animation.module.scss';

export let animation: string;
export let cover: string;

let ref: Element | undefined = undefined;
let coverRef: HTMLDivElement | undefined = undefined;
const dispatch = createEventDispatcher();

function triggerLoaded(animation: any = false) {
dispatch('loaded', {animation: animation === true});
}

const loadAnimation = (path) => {
var animData = {
container: ref,
renderer: 'svg',
loop: true,
autoplay: true,
path: getPublicPath(`/assets/nudge/anim/${path}.json`),
rendererSettings: {
progressiveLoad: true
},
};
const bmAnim = window['bodymovin'].loadAnimation(animData);
bmAnim.addEventListener('data_ready', () => {
if (coverRef) {
Object.assign(coverRef.style, {display: 'none'});
}

triggerLoaded(true);
});
}

onMount(async () => {
await import(/* @vite-ignore */getPublicPath('/assets/nudge/bodymovin.js'));
loadAnimation(animation);
});

</script>

<div class={styles.animation} bind:this={ref}>
{#if cover}
<img
src={getPublicPath(`/assets/nudge/anim/${cover}`)}
alt="icon"
bind:this={coverRef} on:load={triggerLoaded}
/>
{/if}
</div>
Loading
Loading