diff --git a/dashboard/components/button/Button.mocks.tsx b/dashboard/components/button/Button.mocks.tsx index 736305a2a..a2251e8ac 100644 --- a/dashboard/components/button/Button.mocks.tsx +++ b/dashboard/components/button/Button.mocks.tsx @@ -8,7 +8,9 @@ const base: ButtonProps = { size: 'lg', disabled: false, loading: false, - onClick: () => {} + onClick: () => {}, + href: '', + target: '' }; const secondary: ButtonProps = { @@ -63,13 +65,34 @@ const deleteButton: ButtonProps = { onClick: () => {} }; +const linkButton: ButtonProps = { + children: 'Link button', + asLink: true, + style: 'primary', + size: 'lg', + loading: false, + href: 'https://komiser.io' +}; + +const newTabLinkButton: ButtonProps = { + children: 'New Tab Link button', + asLink: true, + style: 'secondary', + size: 'lg', + loading: false, + href: 'https://komiser.io', + target: '_blank' +}; + const mockButtonProps = { base, secondary, ghost, text, dropdown, - deleteButton + deleteButton, + linkButton, + newTabLinkButton }; export default mockButtonProps; diff --git a/dashboard/components/button/Button.stories.tsx b/dashboard/components/button/Button.stories.tsx index a83a23872..87e6eab4d 100644 --- a/dashboard/components/button/Button.stories.tsx +++ b/dashboard/components/button/Button.stories.tsx @@ -65,3 +65,15 @@ export const Delete: Story = { ...mockButtonProps.deleteButton } }; + +export const Link: Story = { + args: { + ...mockButtonProps.linkButton + } +}; + +export const NewTabLink: Story = { + args: { + ...mockButtonProps.newTabLinkButton + } +}; diff --git a/dashboard/components/button/Button.tsx b/dashboard/components/button/Button.tsx index dedb7adec..6393f4673 100644 --- a/dashboard/components/button/Button.tsx +++ b/dashboard/components/button/Button.tsx @@ -12,10 +12,14 @@ export type ButtonProps = { gap?: 'md'; transition?: boolean; onClick?: (e?: any) => void; + asLink?: boolean; + href?: string; + target?: string; }; function Button({ children, + asLink = false, type = 'button', style = 'primary', size = 'md', @@ -24,7 +28,9 @@ function Button({ align, gap, transition = true, - onClick + onClick, + href, + target }: ButtonProps) { const xxs = 'p-1'; const xs = 'py-2 px-4'; @@ -73,21 +79,37 @@ function Button({ if (style === 'text') buttonStyle = text; if (style === 'dropdown') buttonStyle = dropdown; if (style === 'delete') buttonStyle = deleteStyle; + if (asLink) buttonStyle = `${buttonStyle} inline-block sm:w-fit-content`; return buttonStyle; } return ( - + <> + {asLink ? ( + + {loading && } + {children} + + ) : ( + + )} + ); } diff --git a/dashboard/components/dashboard/components/cost-explorer/DashboardCostExplorerCard.tsx b/dashboard/components/dashboard/components/cost-explorer/DashboardCostExplorerCard.tsx index c536d4801..cb6eb9a13 100644 --- a/dashboard/components/dashboard/components/cost-explorer/DashboardCostExplorerCard.tsx +++ b/dashboard/components/dashboard/components/cost-explorer/DashboardCostExplorerCard.tsx @@ -10,8 +10,11 @@ import { import Image from 'next/image'; import { Dispatch, SetStateAction } from 'react'; import { Bar } from 'react-chartjs-2'; -import SelectCheckbox from '../../../select-checkbox/SelectCheckbox'; -import Select from '../../../select/Select'; + +import Button from '@components/button/Button'; +import SelectCheckbox from '@components/select-checkbox/SelectCheckbox'; +import Select from '@components/select/Select'; +import { CloudIcon } from '@components/icons'; import { CostExplorerQueryDateProps, CostExplorerQueryGranularityProps, @@ -115,17 +118,31 @@ function DashboardCostExplorerCard({
{chartData && } {!chartData && ( -
-

- No data for this time period -

- No data to display image +
+
+
+

No data for this time period

+

+ Our cloud version, Tailwarden, supports
+ historical costs from certain cloud providers +

+ +
+ Purplin on a Rocket +
)}
diff --git a/dashboard/components/icons/CloudIcon.tsx b/dashboard/components/icons/CloudIcon.tsx new file mode 100644 index 000000000..5ff0ca98f --- /dev/null +++ b/dashboard/components/icons/CloudIcon.tsx @@ -0,0 +1,29 @@ +import { SVGProps } from 'react'; + +const CloudIcon = (props: SVGProps) => ( + + + + +); + +export default CloudIcon; diff --git a/dashboard/components/icons/Icons.stories.tsx b/dashboard/components/icons/Icons.stories.tsx index dc9fa689a..5c2da12fb 100644 --- a/dashboard/components/icons/Icons.stories.tsx +++ b/dashboard/components/icons/Icons.stories.tsx @@ -9,7 +9,7 @@ const IconsWrapper = (props: SVGProps) => (
{Object.entries(icons).map(([name, Icon]) => (
-
+

{name}

diff --git a/dashboard/components/icons/index.tsx b/dashboard/components/icons/index.tsx index c309a0205..72f0b2681 100644 --- a/dashboard/components/icons/index.tsx +++ b/dashboard/components/icons/index.tsx @@ -7,6 +7,7 @@ export { default as ChevronDownIcon } from './ChevronDownIcon'; export { default as ChevronRightIcon } from './ChevronRightIcon'; export { default as ClearFilterIcon } from './ClearFilterIcon'; export { default as CloseIcon } from './CloseIcon'; +export { default as CloudIcon } from './CloudIcon'; export { default as DeleteIcon } from './DeleteIcon'; export { default as DocumentTextIcon } from './DocumentTextIcon'; export { default as DownloadIcon } from './DownloadIcon'; diff --git a/dashboard/pages/cloud-accounts.tsx b/dashboard/pages/cloud-accounts.tsx index 1b7568579..79be7073b 100644 --- a/dashboard/pages/cloud-accounts.tsx +++ b/dashboard/pages/cloud-accounts.tsx @@ -14,16 +14,32 @@ import CloudAccountDeleteContents from '@components/cloud-account/components/Clo import { useToast } from '@components/toast/ToastProvider'; import EmptyState from '@components/empty-state/EmptyState'; +import Banner from '@components/banner/Banner'; +import Button from '@components/button/Button'; function CloudAccounts() { const [editCloudAccount, setEditCloudAccount] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [isTailwardenBannerDismissed, setIsTailwardenBannerDismissed] = + useState(true); + const { toast, showToast, dismissToast } = useToast(); const router = useRouter(); const currentViewProvider = router.query.view as string; + const hideTailwardenBanner = () => { + setIsTailwardenBannerDismissed(true); + window.localStorage.setItem('tailwardenBannerDismissed', 'true'); + }; + + useEffect(() => { + setIsTailwardenBannerDismissed( + window.localStorage.getItem('tailwardenBannerDismissed') === 'true' + ); + }, []); + const { cloudAccounts, setCloudAccounts, @@ -75,7 +91,7 @@ function CloudAccounts() { {/* Wraps the cloud account page and handles the custom views sidebar */} - +
{filteredCloudAccounts.map(account => ( )} + + {cloudAccounts.length >= 2 && !isTailwardenBannerDismissed && ( +
+ For deeper insights and account-level alerts, make the switch to + Tailwarden — our recommended cloud version for production use.{' '} + + +
+ )} ); } diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js index 0744cc780..a90bf1875 100644 --- a/dashboard/tailwind.config.js +++ b/dashboard/tailwind.config.js @@ -172,7 +172,12 @@ module.exports = { scale: 'scale 250ms ease forwards' }, backgroundImage: { - 'dependency-graph': 'radial-gradient(#EDEBEE 2px, transparent 0)' + 'dependency-graph': 'radial-gradient(#EDEBEE 2px, transparent 0)', + 'empty-cost-explorer': + "url('/assets/img/others/empty-state-cost-explorer.png')" + }, + width: { + 'fit-content': 'fit-content' } } }, diff --git a/dashboard/utils/providerHelper.ts b/dashboard/utils/providerHelper.ts index 56676dc27..e4155e737 100644 --- a/dashboard/utils/providerHelper.ts +++ b/dashboard/utils/providerHelper.ts @@ -142,7 +142,6 @@ const platform: Platform = { } }, - getImgSrc(providerName) { const key = providerName.toLowerCase(); if (key in this.cloudProviders) return this.cloudProviders[key].imgSrc;