NS
NIKITA SONKER

Feature Notification - Copy this React, Tailwind Component to your project

import { Injectable } from '@angular/core'; import * as introJs from 'intro.js'; import { Feature } from '../models/notification.models'; import { FeatureMaintenanceResponse } from '../models/customer.model'; import { Subject } from 'rxjs'; import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; import { FeatureImagePreviewComponent } from 'module/user/src/lib/components/feature-image-preview/feature-image-preview.component'; export class Step { title: string | undefined; intro: string | undefined; } @Injectable({ providedIn: 'root', }) export class IntroJsService { introJS = introJs.default(); currentFeatureNotification: FeatureMaintenanceResponse | undefined; imageClick$ = new Subject<string>(); private dialogRef: DynamicDialogRef | undefined; private clickHandlers: Map<Element, EventListener> = new Map(); constructor(private dialogService: DialogService) { document.addEventListener('click', this.globalImageClickHandler, true); } startTour(featureNotification: FeatureMaintenanceResponse) { if (sessionStorage.getItem('tourSkipped') === 'true') { return; } this.currentFeatureNotification = featureNotification; const tourSteps = this.prepareFeatureList( this.currentFeatureNotification.features ); this.introJS.setOptions({ steps: tourSteps, showProgress: true, showButtons: true, showStepNumbers: false, tooltipClass: 'customTooltip', nextLabel: 'Next', prevLabel: 'Back', doneLabel: 'Finish', disableInteraction: false, scrollToElement: true, }); this.introJS.onexit(() => { this.cleanupEventListeners(); }); this.introJS.onchange((element: HTMLElement) => { this.cleanUpClickHandlers(); setTimeout(() => { this.attachImageClickHandlers(); this.injectSkipButton(); }, 100); }); this.introJS.oncomplete(() => { this.attachImageClickHandlers(); }); this.introJS.start(); } private injectSkipButton() { const tooltip = document.querySelector('.introjs-tooltipbuttons'); if (!tooltip || tooltip.querySelector('.introjs-skip-custom')) return; const buttonWrapper = document.createElement('div'); buttonWrapper.style.display = 'flex'; buttonWrapper.style.justifyContent = 'space-between'; buttonWrapper.style.width = '100%'; const leftDiv = document.createElement('div'); const skipButton = document.createElement('button'); skipButton.innerText = 'Skip Tour'; skipButton.className = 'introjs-button introjs-skip-custom'; skipButton.onclick = () => { sessionStorage.setItem('tourSkipped', 'true'); this.introJS.exit(false); }; leftDiv.appendChild(skipButton); const rightDiv = document.createElement('div'); const buttons = tooltip.querySelectorAll( '.introjs-button:not(.introjs-skip-custom)' ); buttons.forEach((btn) => { (btn as HTMLElement).style.marginLeft = '8px'; rightDiv.appendChild(btn); }); buttonWrapper.appendChild(leftDiv); buttonWrapper.appendChild(rightDiv); tooltip.innerHTML = ''; tooltip.appendChild(buttonWrapper); } private globalImageClickHandler = (event: Event) => { const target = event.target as HTMLElement; if (target && target.classList.contains('clickable-image')) { event.stopPropagation(); const imageUrl = target.getAttribute('data-image-url') || target.getAttribute('src'); if (imageUrl) { this.openImageDialog(imageUrl); } } }; private attachImageClickHandlers() { const images = document.querySelectorAll('.clickable-image'); images.forEach((image) => { image.removeEventListener('click', this.handleImageClick); image.addEventListener('click', this.handleImageClick); }); } private cleanUpClickHandlers() { this.clickHandlers.forEach((handler, element) => { element.removeEventListener('click', handler); }); this.clickHandlers.clear(); } private handleImageClick = (event: Event) => { event.stopPropagation(); const target = event.currentTarget as HTMLElement; const imageUrl = target.getAttribute('data-image-url') || target.getAttribute('src'); if (imageUrl) { this.openImageDialog(imageUrl); } }; private cleanupEventListeners() { const images = document.querySelectorAll('.clickable-image'); images.forEach((image) => { image.removeEventListener('click', this.handleImageClick); }); } prepareFeatureList(featureList: Feature[]) { const steps: any[] = []; featureList.forEach((feature) => { const step = { title: this.currentFeatureNotification?.title, element: document.querySelector('.app-wrapper'), intro: this.prepareFeatureDescriptionHtml(feature), }; steps.push(step); }); return steps; } prepareImageGalleryForFeature(feature: any): string { if (!feature.image_urls || feature.image_urls.length === 0) return ''; let imageGalleryHtmlString = ` <div class="feature-images-container"> <div class="feature-images-grid"> `; feature.image_urls.forEach((imageUrl: string) => { imageGalleryHtmlString += ` <div class="feature-image-wrapper"> <img src="${imageUrl}" data-image-url="${imageUrl}" class="clickable-image" /> </div>`; }); imageGalleryHtmlString += ` </div> </div> `; return imageGalleryHtmlString; } prepareFeatureTitle(feature: Feature) { return `<h2 class='feature-title'>${feature.name}</h2>`; } openImageDialog(imageUrl: string) { if (this.dialogRef) { this.dialogRef.close(); } this.dialogRef = this.dialogService.open(FeatureImagePreviewComponent, { header: 'Image Preview', width: '50%', baseZIndex: 1000000000, appendTo: 'body', styleClass: 'feature-image-dialog', data: { imageUrl: imageUrl, }, }); } prepareFeatureDescriptionHtml(feature: Feature) { const featureTitle = this.prepareFeatureTitle(feature); const featureImageGallery = this.prepareImageGalleryForFeature(feature); return ` <div class="feature-card"> <div class="feature-header"> ${featureTitle} <p class='feature-subtitle'>${feature.description}</p> </div> ${featureImageGallery} </div> `; } } This is the existing code. You can help me. This is the existing code help me now.

Prompt

About

FeatureNotification - Showcase urgent announcements with a pop-up for new features, including titles, descriptions, images, and naviga. Download code free!

Share

Last updated 1 month ago