From 8cbf9aab4278bccdc4490ee0e824a6dd2625e97e Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 20:01:29 +0000 Subject: [PATCH 01/11] Update EnterpriseSnackbar.tsx --- .../GlobalSnackbar/EnterpriseSnackbar.tsx | 51 +++++-------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx index e7c9fbcce3863..725f6ef907c06 100644 --- a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx +++ b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx @@ -1,11 +1,10 @@ -import type { Interpolation, Theme } from "@emotion/react"; import IconButton from "@mui/material/IconButton"; import Snackbar, { type SnackbarProps as MuiSnackbarProps, } from "@mui/material/Snackbar"; -import { type ClassName, useClassName } from "hooks/useClassName"; import { X as XIcon } from "lucide-react"; import type { FC } from "react"; +import { cn } from "utils/cn"; type EnterpriseSnackbarVariant = "error" | "info" | "success"; @@ -35,7 +34,6 @@ export const EnterpriseSnackbar: FC = ({ action, ...snackbarProps }) => { - const content = useClassName(classNames.content(variant), [variant]); return ( = ({ horizontal: "right", }} action={ -
+
{action} - +
} ContentProps={{ ...ContentProps, - className: content, + className: cn( + "rounded-lg bg-surface-secondary text-content-primary shadow", + "py-2 pl-6 pr-4 items-[inherit] border-0 border-l-[4px]", + variantColor(variant) + ), }} onClose={onClose} {...snackbarProps} @@ -67,39 +68,13 @@ export const EnterpriseSnackbar: FC = ({ ); }; -const variantColor = (variant: EnterpriseSnackbarVariant, theme: Theme) => { +const variantColor = (variant: EnterpriseSnackbarVariant) => { switch (variant) { case "error": - return theme.palette.error.main; + return "border-highlight-red"; case "info": - return theme.palette.info.main; + return "border-highlight-sky"; case "success": - return theme.palette.success.main; + return "border-border-success"; } }; - -const classNames = { - content: - (variant: EnterpriseSnackbarVariant): ClassName => - (css, theme) => - css` - border: 1px solid ${theme.palette.divider}; - border-left: 4px solid ${variantColor(variant, theme)}; - border-radius: 8px; - padding: 8px 24px 8px 16px; - box-shadow: ${theme.shadows[6]}; - align-items: inherit; - background-color: ${theme.palette.background.paper}; - color: ${theme.palette.text.secondary}; - `, -}; - -const styles = { - actionWrapper: { - display: "flex", - alignItems: "center", - }, - closeIcon: (theme) => ({ - color: theme.palette.primary.contrastText, - }), -} satisfies Record>; From f1ed76e7e8b697126be30117906056b5eae35f23 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 21:48:30 +0000 Subject: [PATCH 02/11] Update Sidebar.tsx --- site/src/@types/storybook.d.ts | 2 + .../GlobalSnackbar/EnterpriseSnackbar.tsx | 3 +- .../components/Sidebar/Sidebar.stories.tsx | 94 ++++++++++++++----- site/src/components/Sidebar/Sidebar.tsx | 87 ++++------------- 4 files changed, 91 insertions(+), 95 deletions(-) diff --git a/site/src/@types/storybook.d.ts b/site/src/@types/storybook.d.ts index ccdecd690c9c8..599324a291ae4 100644 --- a/site/src/@types/storybook.d.ts +++ b/site/src/@types/storybook.d.ts @@ -8,6 +8,7 @@ import type { } from "api/typesGenerated"; import type { Permissions } from "modules/permissions"; import type { QueryKey } from "react-query"; +import type { ReactRouterAddonStoryParameters } from "storybook-addon-remix-react-router"; declare module "@storybook/react-vite" { type WebSocketEvent = @@ -24,5 +25,6 @@ declare module "@storybook/react-vite" { permissions?: Partial; deploymentValues?: DeploymentValues; deploymentOptions?: SerpentOption[]; + reactRouter?: ReactRouterAddonStoryParameters; } } diff --git a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx index 725f6ef907c06..17036fb2be8e4 100644 --- a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx +++ b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx @@ -34,7 +34,6 @@ export const EnterpriseSnackbar: FC = ({ action, ...snackbarProps }) => { - return ( = ({ className: cn( "rounded-lg bg-surface-secondary text-content-primary shadow", "py-2 pl-6 pr-4 items-[inherit] border-0 border-l-[4px]", - variantColor(variant) + variantColor(variant), ), }} onClose={onClose} diff --git a/site/src/components/Sidebar/Sidebar.stories.tsx b/site/src/components/Sidebar/Sidebar.stories.tsx index 083bffa423fe4..f352118f5f69e 100644 --- a/site/src/components/Sidebar/Sidebar.stories.tsx +++ b/site/src/components/Sidebar/Sidebar.stories.tsx @@ -7,6 +7,7 @@ import { LockIcon, UserIcon, } from "lucide-react"; +import { Outlet } from "react-router"; import { Sidebar, SidebarHeader, SidebarNavItem } from "./Sidebar"; const meta: Meta = { @@ -18,30 +19,73 @@ export default meta; type Story = StoryObj; export const Default: Story = { - args: { - children: ( - - } - title="Jon" - subtitle="jon@coder.com" - /> - - Account - - - Schedule - - - Security - - - SSH Keys - - - Tokens - - - ), + decorators: [ + (Story) => { + return ( +
+ + +
+ ); + }, + ], + render: () => ( + + } + title="Jon" + subtitle="jon@coder.com" + /> + + Account + + + Schedule + + + Security + + + SSH Keys + + + Tokens + + + ), + parameters: { + reactRouter: { + location: { + path: "/account", + }, + routing: [ + { + path: "/", + useStoryElement: true, + children: [ + { + path: "account", + element: <>Account page, + }, + { + path: "schedule", + element: <>Schedule page, + }, + { + path: "security", + element: <>Security page, + }, + { + path: "ssh-keys", + element: <>SSH Keys, + }, + { + path: "tokens", + element: <>Tokens page, + }, + ], + }, + ], + }, }, }; diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index 813835baeb277..249362788d2c0 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -1,7 +1,4 @@ -import { cx } from "@emotion/css"; -import type { CSSObject, Interpolation, Theme } from "@emotion/react"; import { Stack } from "components/Stack/Stack"; -import { type ClassName, useClassName } from "hooks/useClassName"; import type { ElementType, FC, ReactNode } from "react"; import { Link, NavLink } from "react-router"; import { cn } from "utils/cn"; @@ -21,6 +18,11 @@ interface SidebarHeaderProps { linkTo?: string; } +const titleStyles = { + normal: + "text-semibold overflow-hidden whitespace-nowrap text-content-primary", +}; + export const SidebarHeader: FC = ({ avatar, title, @@ -28,7 +30,7 @@ export const SidebarHeader: FC = ({ linkTo, }) => { return ( - + {avatar}
= ({ }} > {linkTo ? ( - + {title} ) : ( - {title} + {title} )} - {subtitle} + + {subtitle} +
); @@ -88,14 +92,18 @@ export const SidebarNavItem: FC = ({ href, icon: Icon, }) => { - const link = useClassName(classNames.link, []); - const activeLink = useClassName(classNames.activeLink, []); - return ( cx([link, isActive && activeLink])} + className={({ isActive }) => + cn( + "block relative text-sm text-inherit mb-px p-3 pl-4 rounded-sm", + "transition-colors no-underline hover:bg-surface-secondary", + isActive && + "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", + ) + } > @@ -104,60 +112,3 @@ export const SidebarNavItem: FC = ({ ); }; - -const styles = { - info: (theme) => ({ - ...(theme.typography.body2 as CSSObject), - marginBottom: 16, - }), - - title: (theme) => ({ - fontWeight: 600, - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - color: theme.palette.text.primary, - textDecoration: "none", - }), - subtitle: (theme) => ({ - color: theme.palette.text.secondary, - fontSize: 12, - overflow: "hidden", - textOverflow: "ellipsis", - }), -} satisfies Record>; - -const classNames = { - link: (css, theme) => css` - color: inherit; - display: block; - font-size: 14px; - text-decoration: none; - padding: 12px 12px 12px 16px; - border-radius: 4px; - transition: background-color 0.15s ease-in-out; - margin-bottom: 1px; - position: relative; - - &:hover { - background-color: ${theme.palette.action.hover}; - } - `, - - activeLink: (css, theme) => css` - background-color: ${theme.palette.action.hover}; - - &:before { - content: ""; - display: block; - width: 3px; - height: 100%; - position: absolute; - left: 0; - top: 0; - background-color: ${theme.palette.primary.main}; - border-top-left-radius: 8px; - border-bottom-left-radius: 8px; - } - `, -} satisfies Record; From c412562d81d163b51fd5f6a2e8ceca4a995d2bb4 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 22:27:28 +0000 Subject: [PATCH 03/11] Update HealthLayout.tsx --- site/src/pages/HealthPage/HealthLayout.tsx | 104 +++++---------------- 1 file changed, 25 insertions(+), 79 deletions(-) diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index e1bdec0973b83..d7f5e2bfc9115 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -1,4 +1,3 @@ -import { cx } from "@emotion/css"; import { useTheme } from "@emotion/react"; import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined"; import ReplayIcon from "@mui/icons-material/Replay"; @@ -9,13 +8,13 @@ import { health, refreshHealth } from "api/queries/debug"; import type { HealthSeverity } from "api/typesGenerated"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; -import { type ClassName, useClassName } from "hooks/useClassName"; import kebabCase from "lodash/fp/kebabCase"; import { DashboardFullPage } from "modules/dashboard/DashboardLayout"; import { type FC, Suspense } from "react"; import { Helmet } from "react-helmet-async"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { NavLink, Outlet } from "react-router"; +import { cn } from "utils/cn"; import { createDayString } from "utils/createDayString"; import { pageTitle } from "utils/page"; import { HealthIcon } from "./Content"; @@ -44,8 +43,12 @@ export const HealthLayout: FC = () => { } as const; const visibleSections = filterVisibleSections(sections); - const link = useClassName(classNames.link, []); - const activeLink = useClassName(classNames.activeLink, []); + const link = ` + text-content-secondary border-none text-sm w-full flex items-center gap-3 + text-left h-9 px-6 cursor-pointer no-underline transition-colors + hover:bg-surface-secondary hover:text-content-primary + `; + const activeLink = "bg-surface-secondary text-content-primary"; if (isLoading) { return ( @@ -71,36 +74,17 @@ export const HealthLayout: FC = () => {
@@ -116,12 +100,12 @@ export const HealthLayout: FC = () => { {isRefreshing ? ( ) : ( - + )}
-
+
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
{ > {healthStatus.healthy ? Object.keys(visibleSections).some((key) => { - const section = - healthStatus[key as keyof typeof visibleSections]; - return section.warnings && section.warnings.length > 0; - }) + const section = + healthStatus[key as keyof typeof visibleSections]; + return section.warnings && section.warnings.length > 0; + }) ? "All systems operational, but performance might be degraded" : "All systems operational" : "Some issues have been detected"}
-
- Last check +
+ Last check {createDayString(healthStatus.time)}
- Version + Version {healthStatus.coder_version}
-
-
+
}> @@ -229,35 +207,3 @@ const filterVisibleSections = (sections: T) => { return visible; }; - -const classNames = { - link: (css, theme) => - css({ - background: "none", - pointerEvents: "auto", - color: theme.palette.text.secondary, - border: "none", - fontSize: 14, - width: "100%", - display: "flex", - alignItems: "center", - gap: 12, - textAlign: "left", - height: 36, - padding: "0 24px", - cursor: "pointer", - textDecoration: "none", - - "&:hover": { - background: theme.palette.action.hover, - color: theme.palette.text.primary, - }, - }), - - activeLink: (css, theme) => - css({ - background: theme.palette.action.hover, - pointerEvents: "none", - color: theme.palette.text.primary, - }), -} satisfies Record; From f92399587a712834add880cac8ee894af4ff96d6 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 22:32:00 +0000 Subject: [PATCH 04/11] deprecate `useClassName` --- site/src/hooks/useClassName.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/src/hooks/useClassName.ts b/site/src/hooks/useClassName.ts index 472e8681a028e..5155d1795a4a5 100644 --- a/site/src/hooks/useClassName.ts +++ b/site/src/hooks/useClassName.ts @@ -5,9 +5,9 @@ import { type DependencyList, useMemo } from "react"; export type ClassName = (cssFn: typeof css, theme: Theme) => string; /** - * An escape hatch for when you really need to manually pass around a - * `className`. Prefer using the `css` prop whenever possible. If you - * can't use that, then this might be helpful for you. + * @deprecated This hook was used as an escape hatch to generate class names + * using emotion when no other styling method would work. There is no valid new + * usage of this hook. Use Tailwind classes instead. */ export function useClassName(styles: ClassName, deps: DependencyList): string { const theme = useTheme(); From acf33a1232fd178d0f800c9762302272d631c459 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 23:00:36 +0000 Subject: [PATCH 05/11] format --- site/src/pages/HealthPage/HealthLayout.tsx | 24 ++++++++-------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index d7f5e2bfc9115..2c691ee2529b1 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -73,19 +73,11 @@ export const HealthLayout: FC = () => { -
-
-
+
+
+
-
+
@@ -116,10 +108,10 @@ export const HealthLayout: FC = () => { > {healthStatus.healthy ? Object.keys(visibleSections).some((key) => { - const section = - healthStatus[key as keyof typeof visibleSections]; - return section.warnings && section.warnings.length > 0; - }) + const section = + healthStatus[key as keyof typeof visibleSections]; + return section.warnings && section.warnings.length > 0; + }) ? "All systems operational, but performance might be degraded" : "All systems operational" : "Some issues have been detected"} From 4946c9a0a8ea3e07ae1dc75c9ef25c52afd7ec64 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 23:12:00 +0000 Subject: [PATCH 06/11] oh --- site/src/components/Sidebar/Sidebar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index 249362788d2c0..f1dc776bdb6a6 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -46,7 +46,7 @@ export const SidebarHeader: FC = ({ ) : ( {title} )} - + {subtitle}
@@ -101,7 +101,7 @@ export const SidebarNavItem: FC = ({ "block relative text-sm text-inherit mb-px p-3 pl-4 rounded-sm", "transition-colors no-underline hover:bg-surface-secondary", isActive && - "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", + "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", ) } > From 709eb72146f43cdf69232ea90b451228b424bdfd Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Wed, 13 Aug 2025 23:15:02 +0000 Subject: [PATCH 07/11] >>>>>:( --- site/src/components/Sidebar/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/Sidebar/Sidebar.tsx b/site/src/components/Sidebar/Sidebar.tsx index f1dc776bdb6a6..a09d9bfbaa517 100644 --- a/site/src/components/Sidebar/Sidebar.tsx +++ b/site/src/components/Sidebar/Sidebar.tsx @@ -101,7 +101,7 @@ export const SidebarNavItem: FC = ({ "block relative text-sm text-inherit mb-px p-3 pl-4 rounded-sm", "transition-colors no-underline hover:bg-surface-secondary", isActive && - "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", + "bg-surface-secondary border-0 border-solid border-l-[3px] border-highlight-sky", ) } > From 29cb9ba70e945ca4cf20adfb54ea52ae88400708 Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Thu, 14 Aug 2025 20:44:48 +0000 Subject: [PATCH 08/11] fix classnames --- .../GlobalSnackbar/EnterpriseSnackbar.tsx | 2 +- site/src/pages/HealthPage/HealthLayout.tsx | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx index 17036fb2be8e4..04b214995c814 100644 --- a/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx +++ b/site/src/components/GlobalSnackbar/EnterpriseSnackbar.tsx @@ -70,7 +70,7 @@ export const EnterpriseSnackbar: FC = ({ const variantColor = (variant: EnterpriseSnackbarVariant) => { switch (variant) { case "error": - return "border-highlight-red"; + return "border-border-destructive"; case "info": return "border-highlight-sky"; case "success": diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index 2c691ee2529b1..da97cdc81abd2 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -97,7 +97,7 @@ export const HealthLayout: FC = () => {
-
+
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
{ > {healthStatus.healthy ? Object.keys(visibleSections).some((key) => { - const section = - healthStatus[key as keyof typeof visibleSections]; - return section.warnings && section.warnings.length > 0; - }) + const section = + healthStatus[key as keyof typeof visibleSections]; + return section.warnings && section.warnings.length > 0; + }) ? "All systems operational, but performance might be degraded" : "All systems operational" : "Some issues have been detected"} @@ -119,7 +119,7 @@ export const HealthLayout: FC = () => {
- Last check + Last check {
- Version + Version Date: Thu, 14 Aug 2025 20:49:44 +0000 Subject: [PATCH 09/11] hmm --- site/src/pages/HealthPage/HealthLayout.tsx | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index da97cdc81abd2..1acf667713ac3 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -19,6 +19,15 @@ import { createDayString } from "utils/createDayString"; import { pageTitle } from "utils/page"; import { HealthIcon } from "./Content"; +const linkStyles = { + normal: ` + text-content-secondary border-none text-sm w-full flex items-center gap-3 + text-left h-9 px-6 cursor-pointer no-underline transition-colors + hover:bg-surface-secondary hover:text-content-primary + ` + active: "bg-surface-secondary text-content-primary", +}; + export const HealthLayout: FC = () => { const theme = useTheme(); const queryClient = useQueryClient(); @@ -43,13 +52,6 @@ export const HealthLayout: FC = () => { } as const; const visibleSections = filterVisibleSections(sections); - const link = ` - text-content-secondary border-none text-sm w-full flex items-center gap-3 - text-left h-9 px-6 cursor-pointer no-underline transition-colors - hover:bg-surface-secondary hover:text-content-primary - `; - const activeLink = "bg-surface-secondary text-content-primary"; - if (isLoading) { return (
@@ -101,10 +103,7 @@ export const HealthLayout: FC = () => { {healthStatus.healthy ? "Healthy" : "Unhealthy"}
{healthStatus.healthy ? Object.keys(visibleSections).some((key) => { @@ -128,7 +127,7 @@ export const HealthLayout: FC = () => {
-
+
Version { key={key} to={`/health/${kebabCase(key)}`} className={({ isActive }) => - cn(link, isActive && activeLink) + cn(linkStyles.normal, isActive && linkStyles.active) } > Date: Thu, 14 Aug 2025 21:40:59 +0000 Subject: [PATCH 10/11] :| --- site/src/pages/HealthPage/HealthLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index 1acf667713ac3..82278bbb2044d 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -24,7 +24,7 @@ const linkStyles = { text-content-secondary border-none text-sm w-full flex items-center gap-3 text-left h-9 px-6 cursor-pointer no-underline transition-colors hover:bg-surface-secondary hover:text-content-primary - ` + `, active: "bg-surface-secondary text-content-primary", }; From 53b5fb1687c74bf027facaaf012d41e14599a49d Mon Sep 17 00:00:00 2001 From: McKayla Washburn Date: Thu, 14 Aug 2025 21:42:56 +0000 Subject: [PATCH 11/11] come oooooon --- site/src/pages/HealthPage/HealthLayout.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/site/src/pages/HealthPage/HealthLayout.tsx b/site/src/pages/HealthPage/HealthLayout.tsx index 82278bbb2044d..86e79aa9ec69e 100644 --- a/site/src/pages/HealthPage/HealthLayout.tsx +++ b/site/src/pages/HealthPage/HealthLayout.tsx @@ -102,15 +102,13 @@ export const HealthLayout: FC = () => {
{healthStatus.healthy ? "Healthy" : "Unhealthy"}
-
+
{healthStatus.healthy ? Object.keys(visibleSections).some((key) => { - const section = - healthStatus[key as keyof typeof visibleSections]; - return section.warnings && section.warnings.length > 0; - }) + const section = + healthStatus[key as keyof typeof visibleSections]; + return section.warnings && section.warnings.length > 0; + }) ? "All systems operational, but performance might be degraded" : "All systems operational" : "Some issues have been detected"} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy