From d23a4a968f5cd31616068627502a6f61e10582d7 Mon Sep 17 00:00:00 2001 From: Vincent T Date: Tue, 16 Sep 2025 11:18:44 -0400 Subject: [PATCH 1/7] Vincent-Hackathon: Frontend: Tutorial: Add foundation for TutorialToolTip and TutorialGuide --- .../common/Tutorial/TutorialGuide.tsx | 298 ++++++++++++++++++ .../common/Tutorial/TutorialToolTip.tsx | 88 ++++++ .../src/components/common/Tutorial/index.ts | 22 ++ .../common/Tutorial/tutorialContent.ts | 37 +++ .../common/Tutorial/tutorialStorage.ts | 54 ++++ .../common/Tutorial/tutorialTopicsMap.ts | 32 ++ .../src/components/common/Tutorial/types.ts | 34 ++ 7 files changed, 565 insertions(+) create mode 100644 frontend/src/components/common/Tutorial/TutorialGuide.tsx create mode 100644 frontend/src/components/common/Tutorial/TutorialToolTip.tsx create mode 100644 frontend/src/components/common/Tutorial/index.ts create mode 100644 frontend/src/components/common/Tutorial/tutorialContent.ts create mode 100644 frontend/src/components/common/Tutorial/tutorialStorage.ts create mode 100644 frontend/src/components/common/Tutorial/tutorialTopicsMap.ts create mode 100644 frontend/src/components/common/Tutorial/types.ts diff --git a/frontend/src/components/common/Tutorial/TutorialGuide.tsx b/frontend/src/components/common/Tutorial/TutorialGuide.tsx new file mode 100644 index 00000000000..9a1b9a547e6 --- /dev/null +++ b/frontend/src/components/common/Tutorial/TutorialGuide.tsx @@ -0,0 +1,298 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InlineIcon } from '@iconify/react'; +import CloseIcon from '@mui/icons-material/Close'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined'; +import { + Accordion, + AccordionDetails, + AccordionSummary, + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + Divider, + IconButton, + Stack, + Typography, +} from '@mui/material'; +import React from 'react'; +import { getActiveTutorial, setActiveTutorial } from './tutorialStorage'; + +export interface TutorialGuideProps { + open: boolean; + onClose: () => void; +} + +export default function TutorialGuide({ open, onClose }: TutorialGuideProps) { + const [active, setActive] = React.useState(getActiveTutorial()); + + React.useEffect(() => { + const onStorage = () => setActive(getActiveTutorial()); + window.addEventListener('storage', onStorage); + return () => window.removeEventListener('storage', onStorage); + }, []); + + const toggleTopic = (topicId: typeof active.topicId) => { + const next = active.topicId === topicId ? null : topicId; + setActiveTutorial(next); + setActive({ topicId: next }); + }; + + const showMeVariant = (topicId: string | null) => + active.topicId === topicId ? 'contained' : 'outlined'; + + return ( + + + Headlamp Quick Start Guide + + + + + + + + + + This quick start guide helps you learn and navigate Headlamp. + + + Click on a topic to expand its question list, choose a question and follow the steps. + + + + + + You can also click the Show Me button to highlight the relevant parts + of the UI. + + When using "Show Me", look for the yellow outline with the lightbulb{' '} + , hovering over the highlighted areas will display additional + information. + + + + + + Topics + + + {/* Clusters (borderless section) */} + + }> + + Clusters + + + + + {/* Q1: start a cluster (outlined question box) */} + + }> + + + How do I start a cluster? + + + + + + +
  • + At the main dashboard, click the Add Cluster button at the + bottom left. +
  • +
  • + On the Add Cluster page, choose Load from Kubeconfig{' '} + if you have a kubeconfig. +
    + + (Alternatively, use the quick-start cluster. You can also add a local + provider via Add Local Cluster Provider.) + +
  • +
  • + After adding, open the cluster overview page to start exploring resources. +
  • +
    +
    +
    + + {/* Q2: delete a cluster (outlined question box) */} + + }> + + + How do I delete a cluster? + + + + + + +
  • + Locate the name of the cluster you would like to remove on the home page. +
  • +
  • + Click the menu icon for that cluster. +
  • +
  • Click the “Delete” option for that cluster.
  • +
    +
    +
    +
    +
    +
    + + + + {/* Resources (borderless section) */} + + }> + + Resources + + + + + {/* Q1: add a resource (outlined question box) */} + + }> + + + How do I add a resource to my cluster? + + + + + + +
  • + Inside the cluster view, click the Create button. +
  • +
  • + Follow the Pod example or paste your own YAML to create any Kubernetes + resource. +
  • +
  • Once created, the resource appears immediately in the dashboard.
  • +
    +
    +
    +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/components/common/Tutorial/TutorialToolTip.tsx b/frontend/src/components/common/Tutorial/TutorialToolTip.tsx new file mode 100644 index 00000000000..54011056dfa --- /dev/null +++ b/frontend/src/components/common/Tutorial/TutorialToolTip.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// notes: +// drop-in wrapper. only shows the yellow highlight when its context's topic +// matches the current active topic from localstorage. +// when inactive: renders the label untouched (no extra DOM, no ARIA noise). + +import { Icon } from '@iconify/react'; +import { Box } from '@mui/material'; +import React from 'react'; +import TooltipLight from '../Tooltip/TooltipLight'; +import { getTutorialText } from './tutorialContent'; +import { getActiveTutorial, TUTORIAL_ACTIVE_KEY } from './tutorialStorage'; +import { contextToTopic } from './tutorialTopicsMap'; +import type { TutorialContextId } from './types'; + +interface TutorialToolTipProps { + context: TutorialContextId; + labelText?: string | React.ReactNode; +} + +export function TutorialToolTip({ context, labelText }: TutorialToolTipProps) { + const [isActive, setIsActive] = React.useState(false); + + React.useEffect(() => { + // helper so we can reuse it on mount + storage changes + const check = () => { + const active = getActiveTutorial(); + const topicForContext = contextToTopic[context]; + setIsActive(!!topicForContext && active.topicId === topicForContext); + }; + + // initial check + check(); + + // react to other places toggling the active topic + const onStorage = (e: StorageEvent) => { + if (!e.key || e.key === TUTORIAL_ACTIVE_KEY) check(); + }; + window.addEventListener('storage', onStorage); + + return () => window.removeEventListener('storage', onStorage); + }, [context]); + + // if the context isn't active, render the label with zero changes + if (!isActive) return <>{labelText}; + + const currentTutorialText = getTutorialText(context); + + // active: wrap with the yellow helper chrome + tooltip + return ( + + + {labelText} + + + + + + ); +} diff --git a/frontend/src/components/common/Tutorial/index.ts b/frontend/src/components/common/Tutorial/index.ts new file mode 100644 index 00000000000..3c3a2fd597e --- /dev/null +++ b/frontend/src/components/common/Tutorial/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './types'; +export * from './tutorialStorage'; +export * from './tutorialTopicsMap'; +export * from './tutorialContent'; +export { default as TutorialGuide } from './TutorialGuide'; +export { TutorialToolTip } from './TutorialToolTip'; diff --git a/frontend/src/components/common/Tutorial/tutorialContent.ts b/frontend/src/components/common/Tutorial/tutorialContent.ts new file mode 100644 index 00000000000..c84c43d2aa6 --- /dev/null +++ b/frontend/src/components/common/Tutorial/tutorialContent.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// notes: +// all tooltip text lives here for now. easy to edit for now +// later we can move this to i18n if needed without touching components. + +import type { StrictRecord, TutorialContextId } from './types'; + +export const tutorialContent: StrictRecord = { + AddClusterButton: + 'This is the Add Cluster button. Click here to add a new Kubernetes cluster to Headlamp.', + LoadFromKubeConfig: + 'Click here to load a cluster using your KubeConfig file. Make sure you have access to the cluster you want to add.', + LoadDemoKubeConfig: + 'Click here to load a demo KubeConfig file. This is useful for testing and learning purposes.', + CreateButton: + 'This is the Create button. Click here to create a new resource in your selected cluster.', +}; + +export function getTutorialText(context: TutorialContextId): string { + // simple safe getter. returns empty string if missing (shouldn't happen if types are right) + return tutorialContent[context] ?? ''; +} diff --git a/frontend/src/components/common/Tutorial/tutorialStorage.ts b/frontend/src/components/common/Tutorial/tutorialStorage.ts new file mode 100644 index 00000000000..d2a694dbc1b --- /dev/null +++ b/frontend/src/components/common/Tutorial/tutorialStorage.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// creating the headlamp.tutorial.active here because I want to be able to add progress tracking later +// thinking I may do this with 'headlamp.tutorial.progress' + +// notes: +// single localstorage key that stores which tutorial topic is active right now. +// using a storage event so open views/components react immediately without reload. + +import type { TutorialTopicId } from './types'; + +export const TUTORIAL_ACTIVE_KEY = 'headlamp.tutorial.active'; + +export interface ActiveTutorialState { + topicId: TutorialTopicId | null; +} + +export function getActiveTutorial(): ActiveTutorialState { + try { + const raw = localStorage.getItem(TUTORIAL_ACTIVE_KEY); + return raw ? JSON.parse(raw) : { topicId: null }; + } catch { + // if something goes weird with parsing, just say "no active topic" + return { topicId: null }; + } +} + +export function setActiveTutorial(topicId: ActiveTutorialState['topicId']) { + const payload = JSON.stringify({ topicId }); + localStorage.setItem(TUTORIAL_ACTIVE_KEY, payload); + + // best-effort notify subscribers (some browsers may not construct storageevent, that's fine) + try { + window.dispatchEvent( + new StorageEvent('storage', { key: TUTORIAL_ACTIVE_KEY, newValue: payload }) + ); + } catch { + // no-op + } +} diff --git a/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts b/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts new file mode 100644 index 00000000000..c6067432437 --- /dev/null +++ b/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// notes: +// map each ui "context" (specific element) to the topic it belongs to. +// only contexts mapped to the currently-active topic will highlight. +// this keeps clusters.addCluster from lighting up resource stuff. + +import type { StrictRecord, TutorialContextId, TutorialTopicId } from './types'; + +export const contextToTopic: StrictRecord = { + // clusters → add cluster + AddClusterButton: 'clusters.addCluster', + LoadFromKubeConfig: 'clusters.addCluster', + LoadDemoKubeConfig: 'clusters.addCluster', + + // resources → add resource + CreateButton: 'resources.addResource', +}; diff --git a/frontend/src/components/common/Tutorial/types.ts b/frontend/src/components/common/Tutorial/types.ts new file mode 100644 index 00000000000..38546268499 --- /dev/null +++ b/frontend/src/components/common/Tutorial/types.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// notes: +// this is the shared types file for the tutorial system. +// keep topic ids and context ids centralized so content/maps/components don't drift. +// add new topics/contexts here first, then reference elsewhere. + +export type TutorialTopicId = + | 'clusters.addCluster' + | 'clusters.deleteCluster' + | 'resources.addResource'; + +export type TutorialContextId = + | 'AddClusterButton' + | 'LoadFromKubeConfig' + | 'LoadDemoKubeConfig' + | 'CreateButton'; + +// helper to keep record types strict (so we don't miss keys) +export type StrictRecord = { [P in K]: V }; From e4c3d91e1cdabee5d53fd987b53da17ec5a79173 Mon Sep 17 00:00:00 2001 From: Vincent T Date: Wed, 17 Sep 2025 17:37:08 -0400 Subject: [PATCH 2/7] Vincent-Hackathon: Frontend: Siderbar: Add guide button feature to sidebar --- frontend/src/components/Sidebar/Sidebar.tsx | 81 ++++++++++++++++++--- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Sidebar/Sidebar.tsx b/frontend/src/components/Sidebar/Sidebar.tsx index e8c5d0981e4..a96eb5ed764 100644 --- a/frontend/src/components/Sidebar/Sidebar.tsx +++ b/frontend/src/components/Sidebar/Sidebar.tsx @@ -30,6 +30,8 @@ import { createRouteURL } from '../../lib/router/createRouteURL'; import { useTypedSelector } from '../../redux/hooks'; import ActionButton from '../common/ActionButton'; import CreateButton from '../common/Resource/CreateButton'; +import { TutorialGuide } from '../common/Tutorial'; +import { TutorialToolTip } from '../common/Tutorial/TutorialToolTip'; import NavigationTabs from './NavigationTabs'; import SidebarItem, { SidebarItemProps } from './SidebarItem'; import { DefaultSidebars, setSidebarSelected, setWhetherSidebarOpen } from './sidebarSlice'; @@ -73,6 +75,10 @@ function AddClusterButton() { const { t } = useTranslation(['translation']); const { isOpen } = useSidebarInfo(); + const buttonText = ( + + ); + return ( {isOpen ? ( @@ -81,13 +87,13 @@ function AddClusterButton() { startIcon={} sx={{ color: theme => theme.palette.sidebar.color }} > - {t('translation|Add Cluster')} + {buttonText} ) : ( history.push(createRouteURL('addCluster'))} icon="mdi:plus-box-outline" - description={t('translation|Add Cluster')} + description={buttonText} color="#adadad" width={38} /> @@ -96,6 +102,36 @@ function AddClusterButton() { ); } +function HelpButton() { + const { isOpen } = useSidebarInfo(); + const { t } = useTranslation(); + const [openGuide, setOpenGuide] = React.useState(false); + const label = t('translation|Guide'); + + return ( + + {isOpen ? ( + + ) : ( + setOpenGuide(true)} + icon="mdi:lightbulb-on-outline" + description={label} + color="#adadad" + width={38} + /> + )} + setOpenGuide(false)} /> + + ); +} + function SidebarToggleButton() { const dispatch = useDispatch(); const { isOpen, isNarrow, canExpand, isTemporary } = useSidebarInfo(); @@ -134,12 +170,19 @@ const DefaultLinkArea = memo((props: { sidebarName: string; isOpen: boolean }) = - {isElectron() && } + + + {isElectron() && } + @@ -148,7 +191,27 @@ const DefaultLinkArea = memo((props: { sidebarName: string; isOpen: boolean }) = return ( - + + + + + + { From 40c243f2857a3b545fe920d19a0fd92fdd6ddc44 Mon Sep 17 00:00:00 2001 From: Vincent T Date: Tue, 16 Sep 2025 11:25:44 -0400 Subject: [PATCH 3/7] Vincent-Hackathon: Frontend: ActionButton: Update description to also take react nodes --- frontend/src/components/common/ActionButton/ActionButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/common/ActionButton/ActionButton.tsx b/frontend/src/components/common/ActionButton/ActionButton.tsx index 682c43c481e..b1f2a64cab8 100644 --- a/frontend/src/components/common/ActionButton/ActionButton.tsx +++ b/frontend/src/components/common/ActionButton/ActionButton.tsx @@ -26,7 +26,7 @@ export type ButtonStyle = 'action' | 'menu'; export interface ActionButtonProps { /** A short description of the action. */ - description: string; + description: string | React.ReactNode; /** Either a string icon, or imported icon. */ icon: string | IconifyIcon; /** The action when it's activated. */ From 3c824bb6d7af997e841899876059263e5cc01f4b Mon Sep 17 00:00:00 2001 From: Vincent T Date: Tue, 16 Sep 2025 11:20:25 -0400 Subject: [PATCH 4/7] Vincent-Hackathon: Frontend: Siderbar: Add tutorial tooltip for AddCluster --- .../App/CreateCluster/AddCluster.tsx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/App/CreateCluster/AddCluster.tsx b/frontend/src/components/App/CreateCluster/AddCluster.tsx index 2240bd9fef6..183f7d704c2 100644 --- a/frontend/src/components/App/CreateCluster/AddCluster.tsx +++ b/frontend/src/components/App/CreateCluster/AddCluster.tsx @@ -31,6 +31,7 @@ import { useTypedSelector } from '../../../redux/hooks'; import { DialogProps } from '../../common/Dialog'; import { PageGrid } from '../../common/Resource'; import SectionBox from '../../common/SectionBox'; +import { TutorialToolTip } from '../../common/Tutorial/TutorialToolTip'; function AddClusterProvider({ title, icon, description, url }: ClusterProviderInfo) { const history = useHistory(); @@ -66,6 +67,35 @@ export default function AddCluster(props: DialogProps & { onChoice: () => void } entry => entry.url === '/plugin-catalog' ); + // Hackathon WIP for tutorial mode + // console.log('current tutorial mode', localStorage.getItem('tutorialMode')); + // const isTutorialMode = localStorage.getItem('tutorialMode') === 'true'; + + // const [loadFromKubeConfigDescription, setloadFromKubeConfigDescription] = React.useState< + // string | React.ReactNode + // >('loading..'); + + // React.useEffect(() => { + // if (isTutorialMode) { + // setloadFromKubeConfigDescription( + // + // ); + // } else { + // setloadFromKubeConfigDescription(t('translation|Load from KubeConfig')); + // } + // }, [isTutorialMode, loadFromKubeConfigDescription, t]); + // need to clean later + + const buttonText = ( + + ); + return ( @@ -82,7 +112,7 @@ export default function AddCluster(props: DialogProps & { onChoice: () => void } onClick={() => history.push(createRouteURL('loadKubeConfig'))} startIcon={} > - {t('translation|Load from KubeConfig')} + {buttonText} From 636b33cd7738b86a558d9d0272374a4ce05b4f2a Mon Sep 17 00:00:00 2001 From: Vincent T Date: Tue, 16 Sep 2025 11:22:03 -0400 Subject: [PATCH 5/7] Vincent-Hackathon: Frontend: kubeConfigLoader: Add tutorial tooltip for kubeConfigLoader --- .../components/cluster/KubeConfigLoader.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/components/cluster/KubeConfigLoader.tsx b/frontend/src/components/cluster/KubeConfigLoader.tsx index 1dd1ed49dd9..ba8ad090874 100644 --- a/frontend/src/components/cluster/KubeConfigLoader.tsx +++ b/frontend/src/components/cluster/KubeConfigLoader.tsx @@ -36,6 +36,7 @@ import { setStatelessConfig } from '../../redux/configSlice'; import { DialogTitle } from '../common/Dialog'; import { DropZoneBox } from '../common/DropZoneBox'; import Loader from '../common/Loader'; +import { TutorialToolTip } from '../common/Tutorial/TutorialToolTip'; import { ClusterDialog } from './Chooser'; interface Cluster { @@ -226,6 +227,13 @@ function KubeConfigLoader() { } } + const buttonText = ( + + ); + function renderSwitch() { switch (state) { case Step.LoadKubeConfig: @@ -246,6 +254,16 @@ function KubeConfigLoader() { {t('translation|Choose file')} + + {/* clean later: button for demo */} + From 5b95f0d8df0a68ab11ead20fd38e9e7ca18b2a1d Mon Sep 17 00:00:00 2001 From: Vincent T Date: Tue, 16 Sep 2025 11:26:24 -0400 Subject: [PATCH 6/7] Vincent-Hackathon: Frontend: CreateButton: Add tutorial tooltip for create button --- frontend/src/components/common/Resource/CreateButton.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/common/Resource/CreateButton.tsx b/frontend/src/components/common/Resource/CreateButton.tsx index 6c9cae7467a..d245c6e7225 100644 --- a/frontend/src/components/common/Resource/CreateButton.tsx +++ b/frontend/src/components/common/Resource/CreateButton.tsx @@ -26,6 +26,7 @@ import { useTranslation } from 'react-i18next'; import { useSelectedClusters } from '../../../lib/k8s'; import { Activity } from '../../activity/Activity'; import ActionButton from '../ActionButton'; +import { TutorialToolTip } from '../Tutorial/TutorialToolTip'; import EditorDialog from './EditorDialog'; interface CreateButtonProps { @@ -53,6 +54,8 @@ export default function CreateButton(props: CreateButtonProps) { } }, [clusters]); + const buttonText = ; + const openActivity = () => { const id = 'create-button'; Activity.launch({ @@ -128,7 +131,7 @@ export default function CreateButton(props: CreateButtonProps) { }, })} > - {t('translation|Create')} + {buttonText} )} From 1d3d4686e9eba79756fb443526586edfaa5f8b01 Mon Sep 17 00:00:00 2001 From: Vincent T Date: Mon, 22 Sep 2025 15:41:19 -0400 Subject: [PATCH 7/7] Vincent-Hackathon: Frontend: Add demo button for editor dialog --- .../src/components/common/Resource/EditorDialog.tsx | 13 +++++++++++++ .../components/common/Tutorial/tutorialContent.ts | 2 ++ .../components/common/Tutorial/tutorialTopicsMap.ts | 1 + frontend/src/components/common/Tutorial/types.ts | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/common/Resource/EditorDialog.tsx b/frontend/src/components/common/Resource/EditorDialog.tsx index f283a840f53..771eaad3f8c 100644 --- a/frontend/src/components/common/Resource/EditorDialog.tsx +++ b/frontend/src/components/common/Resource/EditorDialog.tsx @@ -47,6 +47,7 @@ import ConfirmButton from '../ConfirmButton'; import { Dialog, DialogProps } from '../Dialog'; import Loader from '../Loader'; import Tabs from '../Tabs'; +import { TutorialToolTip } from '../Tutorial'; import DocsViewer from './DocsViewer'; import SimpleEditor from './SimpleEditor'; import { UploadDialog } from './UploadDialog'; @@ -447,6 +448,18 @@ export default function EditorDialog(props: EditorDialogProps) { > {t('translation|Upload File/URL')} + + {/* clean later: hackathon demo button */} + diff --git a/frontend/src/components/common/Tutorial/tutorialContent.ts b/frontend/src/components/common/Tutorial/tutorialContent.ts index c84c43d2aa6..6a9f27f8c84 100644 --- a/frontend/src/components/common/Tutorial/tutorialContent.ts +++ b/frontend/src/components/common/Tutorial/tutorialContent.ts @@ -29,6 +29,8 @@ export const tutorialContent: StrictRecord = { 'Click here to load a demo KubeConfig file. This is useful for testing and learning purposes.', CreateButton: 'This is the Create button. Click here to create a new resource in your selected cluster.', + CreateDemoResource: + 'Click here to create a demo resource in your selected cluster. This is useful for testing and learning purposes.', }; export function getTutorialText(context: TutorialContextId): string { diff --git a/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts b/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts index c6067432437..ca9f00497a1 100644 --- a/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts +++ b/frontend/src/components/common/Tutorial/tutorialTopicsMap.ts @@ -29,4 +29,5 @@ export const contextToTopic: StrictRecord = // resources → add resource CreateButton: 'resources.addResource', + CreateDemoResource: 'resources.addResource', }; diff --git a/frontend/src/components/common/Tutorial/types.ts b/frontend/src/components/common/Tutorial/types.ts index 38546268499..32dceeb88bc 100644 --- a/frontend/src/components/common/Tutorial/types.ts +++ b/frontend/src/components/common/Tutorial/types.ts @@ -28,7 +28,8 @@ export type TutorialContextId = | 'AddClusterButton' | 'LoadFromKubeConfig' | 'LoadDemoKubeConfig' - | 'CreateButton'; + | 'CreateButton' + | 'CreateDemoResource'; // helper to keep record types strict (so we don't miss keys) export type StrictRecord = { [P in K]: V };