Skip to content

Commit

Permalink
[PAYG] [dashboard] Onboarding: Product Educational content in `/ work…
Browse files Browse the repository at this point in the history
…spaces` view (#20021)

* intial commit

* Documentation links

* Update YouTube Videos

* cleanup + performant YT method

* Static section of `Personalised for you` 😜

* Add blog banners

* Minor fixes

* Educational content should only be in Gitpod PAYG & minor fixes

* nit fix

* nti fix

* reorder docs link

* nit fix

* final nit fix :)

* fix the label issue, video bug, line break in banner

* use `flex-1`

* bring back old description copy for header

* improve spacing

* Optimize VideoCarousel rendering and key usage

- Render only the current video to improve performance
- Remove redundant key prop from lite-youtube component
- Ensure unique keys for list items in VideoCarousel

* Enhance VideoCarousel button accessibility

- Add focus rings to video selection buttons
- Improve keyboard navigation with visible focus indicators
  • Loading branch information
Siddhant-K-code authored Jul 11, 2024
1 parent 1f5bc61 commit ccca3a6
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 70 deletions.
2 changes: 1 addition & 1 deletion components/dashboard/src/Analytics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export const trackButtonOrAnchor = (target: HTMLAnchorElement | HTMLButtonElemen

let trackingMsg: TrackDashboardClick = {
path: window.location.pathname,
label: target.textContent || undefined,
label: target.ariaLabel || target.textContent || undefined,
};

if (target instanceof HTMLButtonElement || target instanceof HTMLDivElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type PageHeadingProps = {
};
export const PageHeading: FC<PageHeadingProps> = ({ title, subtitle }) => {
return (
<div className="flex flex-row flex-wrap justify-between py-8 gap-2">
<div className="flex flex-row flex-wrap justify-between py-5 gap-2">
<div>
<Heading1>{title}</Heading1>
{subtitle && <Subheading>{subtitle}</Subheading>}
Expand Down
14 changes: 14 additions & 0 deletions components/dashboard/src/icons/gitpod-stroked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions components/dashboard/src/workspaces/BlogBanners.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2024 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import React, { useEffect, useState } from "react";
import blogBannerBg from "../images/blog-banner-bg.png";

const banners = [
{
type: "Blog Post",
title: "Gitpod Enterprise:<br/> Self-hosted, not self-managed",
link: "https://www.gitpod.io/blog/self-hosted-not-self-managed",
},
{
type: "Customer Story",
title: "Thousands of hours spent on VM-based development environments reduced to zero using Gitpod",
link: "https://www.gitpod.io/customers/kingland",
},
{
type: "Gartner Report",
title: `"By 2026, 60% of cloud workloads will be built and deployed using CDE's"`,
link: "https://www.gitpod.io/blog/gartner-2023-cde-hypecycle",
},
{
type: "Webinar Series",
title: "The Platform Engineering maturity model",
link: "https://www.gitpod.io/events#platform-maturity-model-series",
},
];

const initialBannerIndex = 0; // Index for "Self-hosted, not self-managed"

export const BlogBanners: React.FC = () => {
const [currentBannerIndex, setCurrentBannerIndex] = useState(initialBannerIndex);

useEffect(() => {
const storedBannerData = localStorage.getItem("blog-banner-data");
const currentTime = new Date().getTime();

if (storedBannerData) {
const { lastIndex, lastTime } = JSON.parse(storedBannerData);

if (currentTime - lastTime >= 2 * 24 * 60 * 60 * 1000) {
// 2 days in milliseconds
const nextIndex = getRandomBannerIndex(lastIndex);
setCurrentBannerIndex(nextIndex);
localStorage.setItem(
"blog-banner-data",
JSON.stringify({ lastIndex: nextIndex, lastTime: currentTime }),
);
} else {
setCurrentBannerIndex(lastIndex);
}
} else {
setCurrentBannerIndex(initialBannerIndex);
localStorage.setItem(
"blog-banner-data",
JSON.stringify({ lastIndex: initialBannerIndex, lastTime: currentTime }),
);
}
}, []);

const getRandomBannerIndex = (excludeIndex: number) => {
let nextIndex;
do {
nextIndex = Math.floor(Math.random() * banners.length);
} while (nextIndex === excludeIndex || nextIndex === initialBannerIndex);
return nextIndex;
};

return (
<div className="flex flex-col">
<a
href={banners[currentBannerIndex].link}
target="_blank"
rel="noopener noreferrer"
className="bg-pk-surface rounded-lg overflow-hidden flex flex-col gap-2 text-decoration-none text-inherit max-w-[320px] border border-gray-200 dark:border-gray-800 hover:shadow"
aria-label={banners[currentBannerIndex].type + " - " + banners[currentBannerIndex].title}
style={{
backgroundPosition: "top left",
backgroundRepeat: "no-repeat",
backgroundImage: `url(${blogBannerBg})`,
backgroundSize: "contain",
}}
>
<div className="flex flex-col gap-8 mt-6 ml-4 max-w-[320px] overflow-wrap min-h-fit pb-4">
<div className="bg-pk-surface-invert w-fit text-pk-content-invert-primary text-sm leading-[18px] font-bold rounded-2xl py-1 px-4">
{banners[currentBannerIndex].type}
</div>
<div
className="text-base font-semibold text-pk-content-primary max-w-[285px]"
dangerouslySetInnerHTML={{ __html: banners[currentBannerIndex].title }}
/>
</div>
</a>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import "lite-youtube-embed/src/lite-yt-embed.css";
import "lite-youtube-embed/src/lite-yt-embed";

declare global {
interface Window {
onYouTubeIframeAPIReady: () => void;
YT: any;
}
namespace JSX {
interface IntrinsicElements {
"lite-youtube": any;
Expand Down
81 changes: 81 additions & 0 deletions components/dashboard/src/workspaces/VideoCarousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2024 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import React, { useState } from "react";
import { trackVideoClick } from "../Analytics";

import "lite-youtube-embed/src/lite-yt-embed.css";
import "lite-youtube-embed/src/lite-yt-embed";

interface Video {
id: string;
title: string;
analyticsLabel: string;
}

const videos: Video[] = [
{ id: "1ZBN-b2cIB8", title: "Gitpod in 120 seconds", analyticsLabel: "gitpod-demo" },
{ id: "zhZNnzFlZnY", title: "Getting started with Gitpod", analyticsLabel: "getting-started-with-gitpod" },
{ id: "kuoHM2bpBqY", title: "Fully automate your dev setup", analyticsLabel: "automate-gitpod-setup" },
{ id: "_CwFzCbAsoU", title: "Personalise your workspace", analyticsLabel: "personalise-gitpod-workspace" },
];

declare global {
namespace JSX {
interface IntrinsicElements {
"lite-youtube": any;
}
}
}

export const VideoCarousel: React.FC = () => {
const [currentVideo, setCurrentVideo] = useState(0);

const handleDotClick = (index: number) => {
setCurrentVideo(index);
};

const onPlayerStateChange = (index: number) => {
trackVideoClick(videos[index].analyticsLabel);
};

return (
<div className="video-carousel">
<div className="video-container">
{videos.map((video, index) => (
<div key={video.id} style={{ display: index === currentVideo ? "block" : "none" }}>
{index === currentVideo && (
<lite-youtube
videoid={video.id}
style={{
width: "320px",
height: "180px",
}}
class="rounded-lg"
playlabel={video.title}
onClick={() => onPlayerStateChange(index)}
></lite-youtube>
)}
</div>
))}
</div>
<div className="flex justify-center space-x-2 mt-2">
{videos.map((_, index) => (
<button
key={index}
className={`w-3 h-3 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-kumquat-dark transition-colors duration-200 ease-in-out ${
index === currentVideo
? "bg-kumquat-dark"
: "bg-gray-300 dark:bg-gray-600 hover:bg-kumquat-light dark:hover:bg-kumquat-light"
}`}
onClick={() => handleDotClick(index)}
aria-label={`Go to video ${index + 1}`}
></button>
))}
</div>
</div>
);
};
5 changes: 4 additions & 1 deletion components/dashboard/src/workspaces/WorkspaceEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ export const WorkspaceEntry: FunctionComponent<Props> = ({ info, shortVersion })
<div className="min-w-4">
<GitBranchIcon className="h-4 w-4" />
</div>
<Tooltip content={currentBranch} className="truncate overflow-ellipsis">
<Tooltip
content={currentBranch}
className="truncate overflow-ellipsis max-w-[120px] w-auto"
>
{currentBranch}
</Tooltip>
</div>
Expand Down
Loading

0 comments on commit ccca3a6

Please sign in to comment.