mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 03:20:46 +02:00
admin onboarding (#4863)
# Description of Changes <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: EthanHealy01 <[email protected]>
This commit is contained in:
co-authored by
EthanHealy01
parent
ebf4bab80b
commit
3cf89b6ede
@@ -6,3 +6,28 @@
|
||||
ry: 8px;
|
||||
filter: drop-shadow(0 0 10px var(--mantine-primary-color-filled));
|
||||
}
|
||||
|
||||
/* Add glowing border to navigation items during admin tour */
|
||||
.modal-nav-item.tour-nav-glow {
|
||||
position: relative;
|
||||
box-shadow:
|
||||
0 0 0 2px var(--mantine-primary-color-filled),
|
||||
0 0 15px var(--mantine-primary-color-filled),
|
||||
inset 0 0 15px rgba(59, 130, 246, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% {
|
||||
box-shadow:
|
||||
0 0 0 3px var(--mantine-primary-color-filled),
|
||||
0 0 20px var(--mantine-primary-color-filled),
|
||||
inset 0 0 20px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0 0 0 3px var(--mantine-primary-color-filled),
|
||||
0 0 30px var(--mantine-primary-color-filled),
|
||||
inset 0 0 30px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { CloseButton, ActionIcon } from '@mantine/core';
|
||||
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
|
||||
import { useTourOrchestration } from '@app/contexts/TourOrchestrationContext';
|
||||
import { useAdminTourOrchestration } from '@app/contexts/AdminTourOrchestrationContext';
|
||||
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import TourWelcomeModal from '@app/components/onboarding/TourWelcomeModal';
|
||||
@@ -32,6 +33,18 @@ enum TourStep {
|
||||
WRAP_UP,
|
||||
}
|
||||
|
||||
enum AdminTourStep {
|
||||
WELCOME,
|
||||
CONFIG_BUTTON,
|
||||
SETTINGS_OVERVIEW,
|
||||
TEAMS_AND_USERS,
|
||||
SYSTEM_CUSTOMIZATION,
|
||||
DATABASE_SECTION,
|
||||
CONNECTIONS_SECTION,
|
||||
ADMIN_TOOLS,
|
||||
WRAP_UP,
|
||||
}
|
||||
|
||||
function TourContent() {
|
||||
const { isOpen } = useOnboarding();
|
||||
const { setIsOpen, setCurrentStep } = useTour();
|
||||
@@ -54,8 +67,36 @@ function TourContent() {
|
||||
|
||||
export default function OnboardingTour() {
|
||||
const { t } = useTranslation();
|
||||
const { completeTour, showWelcomeModal, setShowWelcomeModal, startTour } = useOnboarding();
|
||||
const { completeTour, showWelcomeModal, setShowWelcomeModal, startTour, tourType, isOpen } = useOnboarding();
|
||||
const { openFilesModal, closeFilesModal } = useFilesModalContext();
|
||||
|
||||
// Helper to add glow to multiple elements
|
||||
const addGlowToElements = (selectors: string[]) => {
|
||||
selectors.forEach(selector => {
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
if (selector === '[data-tour="settings-content-area"]') {
|
||||
element.classList.add('tour-content-glow');
|
||||
} else {
|
||||
element.classList.add('tour-nav-glow');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Helper to remove all glows
|
||||
const removeAllGlows = () => {
|
||||
document.querySelectorAll('.tour-content-glow').forEach(el => el.classList.remove('tour-content-glow'));
|
||||
document.querySelectorAll('.tour-nav-glow').forEach(el => el.classList.remove('tour-nav-glow'));
|
||||
};
|
||||
|
||||
// Cleanup glows when tour closes
|
||||
React.useEffect(() => {
|
||||
if (!isOpen) {
|
||||
removeAllGlows();
|
||||
}
|
||||
return () => removeAllGlows();
|
||||
}, [isOpen]);
|
||||
const {
|
||||
saveWorkbenchState,
|
||||
restoreWorkbenchState,
|
||||
@@ -70,6 +111,13 @@ export default function OnboardingTour() {
|
||||
modifyCropSettings,
|
||||
executeTool,
|
||||
} = useTourOrchestration();
|
||||
const {
|
||||
saveAdminState,
|
||||
restoreAdminState,
|
||||
openConfigModal,
|
||||
navigateToSection,
|
||||
scrollNavToSection,
|
||||
} = useAdminTourOrchestration();
|
||||
|
||||
// Define steps as object keyed by enum - TypeScript ensures all keys are present
|
||||
const stepsConfig: Record<TourStep, StepType> = {
|
||||
@@ -202,8 +250,125 @@ export default function OnboardingTour() {
|
||||
},
|
||||
};
|
||||
|
||||
// Convert to array using enum's numeric ordering
|
||||
const steps = Object.values(stepsConfig);
|
||||
// Define admin tour steps
|
||||
const adminStepsConfig: Record<AdminTourStep, StepType> = {
|
||||
[AdminTourStep.WELCOME]: {
|
||||
selector: '[data-tour="config-button"]',
|
||||
content: t('adminOnboarding.welcome', "Welcome to the <strong>Admin Tour</strong>! Let's explore the powerful enterprise features and settings available to system administrators."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
saveAdminState();
|
||||
},
|
||||
},
|
||||
[AdminTourStep.CONFIG_BUTTON]: {
|
||||
selector: '[data-tour="config-button"]',
|
||||
content: t('adminOnboarding.configButton', "Click the <strong>Config</strong> button to access all system settings and administrative controls."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
actionAfter: () => {
|
||||
openConfigModal();
|
||||
},
|
||||
},
|
||||
[AdminTourStep.SETTINGS_OVERVIEW]: {
|
||||
selector: '.modal-nav',
|
||||
content: t('adminOnboarding.settingsOverview', "This is the <strong>Settings Panel</strong>. Admin settings are organised by category for easy navigation."),
|
||||
position: 'right',
|
||||
padding: 0,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
},
|
||||
},
|
||||
[AdminTourStep.TEAMS_AND_USERS]: {
|
||||
selector: '[data-tour="admin-people-nav"]',
|
||||
highlightedSelectors: ['[data-tour="admin-people-nav"]', '[data-tour="admin-teams-nav"]', '[data-tour="settings-content-area"]'],
|
||||
content: t('adminOnboarding.teamsAndUsers', "Manage <strong>Teams</strong> and individual users here. You can invite new users via email, shareable links, or create custom accounts for them yourself."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
navigateToSection('people');
|
||||
setTimeout(() => {
|
||||
addGlowToElements(['[data-tour="admin-people-nav"]', '[data-tour="admin-teams-nav"]', '[data-tour="settings-content-area"]']);
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
[AdminTourStep.SYSTEM_CUSTOMIZATION]: {
|
||||
selector: '[data-tour="admin-adminGeneral-nav"]',
|
||||
highlightedSelectors: ['[data-tour="admin-adminGeneral-nav"]', '[data-tour="admin-adminFeatures-nav"]', '[data-tour="admin-adminEndpoints-nav"]', '[data-tour="settings-content-area"]'],
|
||||
content: t('adminOnboarding.systemCustomization', "We have extensive ways to customise the UI: <strong>System Settings</strong> let you change the app name and languages, <strong>Features</strong> allows server certificate management, and <strong>Endpoints</strong> lets you enable or disable specific tools for your users."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
navigateToSection('adminGeneral');
|
||||
setTimeout(() => {
|
||||
addGlowToElements(['[data-tour="admin-adminGeneral-nav"]', '[data-tour="admin-adminFeatures-nav"]', '[data-tour="admin-adminEndpoints-nav"]', '[data-tour="settings-content-area"]']);
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
[AdminTourStep.DATABASE_SECTION]: {
|
||||
selector: '[data-tour="admin-adminDatabase-nav"]',
|
||||
highlightedSelectors: ['[data-tour="admin-adminDatabase-nav"]', '[data-tour="settings-content-area"]'],
|
||||
content: t('adminOnboarding.databaseSection', "For advanced production environments, we have settings to allow <strong>external database hookups</strong> so you can integrate with your existing infrastructure."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
navigateToSection('adminDatabase');
|
||||
setTimeout(() => {
|
||||
addGlowToElements(['[data-tour="admin-adminDatabase-nav"]', '[data-tour="settings-content-area"]']);
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
[AdminTourStep.CONNECTIONS_SECTION]: {
|
||||
selector: '[data-tour="admin-adminConnections-nav"]',
|
||||
highlightedSelectors: ['[data-tour="admin-adminConnections-nav"]', '[data-tour="settings-content-area"]'],
|
||||
content: t('adminOnboarding.connectionsSection', "The <strong>Connections</strong> section supports various login methods including custom SSO and SAML providers like Google and GitHub, plus email integrations for notifications and communications."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
navigateToSection('adminConnections');
|
||||
setTimeout(() => {
|
||||
addGlowToElements(['[data-tour="admin-adminConnections-nav"]', '[data-tour="settings-content-area"]']);
|
||||
}, 100);
|
||||
},
|
||||
actionAfter: async () => {
|
||||
// Scroll for the NEXT step before it shows
|
||||
await scrollNavToSection('adminAudit');
|
||||
},
|
||||
},
|
||||
[AdminTourStep.ADMIN_TOOLS]: {
|
||||
selector: '[data-tour="admin-adminAudit-nav"]',
|
||||
highlightedSelectors: ['[data-tour="admin-adminAudit-nav"]', '[data-tour="admin-adminUsage-nav"]', '[data-tour="settings-content-area"]'],
|
||||
content: t('adminOnboarding.adminTools', "Finally, we have advanced administration tools like <strong>Auditing</strong> to track system activity and <strong>Usage Analytics</strong> to monitor how your users interact with the platform."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
// Just navigate, scroll already happened in previous step
|
||||
removeAllGlows();
|
||||
navigateToSection('adminAudit');
|
||||
setTimeout(() => {
|
||||
addGlowToElements(['[data-tour="admin-adminAudit-nav"]', '[data-tour="admin-adminUsage-nav"]', '[data-tour="settings-content-area"]']);
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
[AdminTourStep.WRAP_UP]: {
|
||||
selector: '[data-tour="help-button"]',
|
||||
content: t('adminOnboarding.wrapUp', "That's the admin tour! You've seen the enterprise features that make Stirling PDF a powerful, customisable solution for organisations. Access this tour anytime from the <strong>Help</strong> menu."),
|
||||
position: 'right',
|
||||
padding: 10,
|
||||
action: () => {
|
||||
removeAllGlows();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Select steps based on tour type
|
||||
const steps = tourType === 'admin'
|
||||
? Object.values(adminStepsConfig)
|
||||
: Object.values(stepsConfig);
|
||||
|
||||
const advanceTour = ({ setCurrentStep, currentStep, steps, setIsOpen }: {
|
||||
setCurrentStep: (value: number | ((prev: number) => number)) => void;
|
||||
@@ -213,7 +378,11 @@ export default function OnboardingTour() {
|
||||
}) => {
|
||||
if (steps && currentStep === steps.length - 1) {
|
||||
setIsOpen(false);
|
||||
restoreWorkbenchState();
|
||||
if (tourType === 'admin') {
|
||||
restoreAdminState();
|
||||
} else {
|
||||
restoreWorkbenchState();
|
||||
}
|
||||
completeTour();
|
||||
} else if (steps) {
|
||||
setCurrentStep((s) => (s === steps.length - 1 ? 0 : s + 1));
|
||||
@@ -222,7 +391,11 @@ export default function OnboardingTour() {
|
||||
|
||||
const handleCloseTour = ({ setIsOpen }: { setIsOpen: (value: boolean) => void }) => {
|
||||
setIsOpen(false);
|
||||
restoreWorkbenchState();
|
||||
if (tourType === 'admin') {
|
||||
restoreAdminState();
|
||||
} else {
|
||||
restoreWorkbenchState();
|
||||
}
|
||||
completeTour();
|
||||
};
|
||||
|
||||
@@ -243,7 +416,9 @@ export default function OnboardingTour() {
|
||||
}}
|
||||
/>
|
||||
<TourProvider
|
||||
key={tourType}
|
||||
steps={steps}
|
||||
maskClassName={tourType === 'admin' ? 'admin-tour-mask' : undefined}
|
||||
onClickClose={handleCloseTour}
|
||||
onClickMask={advanceTour}
|
||||
onClickHighlighted={(e, clickProps) => {
|
||||
|
||||
Reference in New Issue
Block a user