diff --git a/src/pages/sidepanel/SidePanel.tsx b/src/pages/sidepanel/SidePanel.tsx index f81dca6..8a88bd7 100644 --- a/src/pages/sidepanel/SidePanel.tsx +++ b/src/pages/sidepanel/SidePanel.tsx @@ -104,7 +104,15 @@ const SidePanel: React.FC = ({ key, isEnabled }) => { ) : ( <> - + } /> ({ position: 'fixed', bottom: 0, width: '100%', - height: 'auto', + height: '4.375rem', // 70px (Figma spec) display: 'flex', justifyContent: 'space-between', - padding: theme.spacing(1.5, 2), + alignItems: 'center', + gap: '0.625rem', // 10px (Figma spec) + padding: '0.75rem 1rem', // 12px top/bottom, 16px left/right (Figma spec) + backgroundColor: theme.palette.background.default, + backdropFilter: 'blur(13.3px)', // 13.3px - Figma blur value + WebkitBackdropFilter: 'blur(13.3px)', // Safari support cursor: 'default', transition: 'none', '&:hover': { diff --git a/src/pages/sidepanel/components/CustomTabPanel.tsx b/src/pages/sidepanel/components/CustomTabPanel.tsx index bf58406..c573055 100644 --- a/src/pages/sidepanel/components/CustomTabPanel.tsx +++ b/src/pages/sidepanel/components/CustomTabPanel.tsx @@ -9,9 +9,9 @@ interface TabPanelProps { } const TabPanelContainer = styled(Box)(({ theme }) => ({ - padding: theme.spacing(1), - height: 'calc(100vh - 185px)', - overflow: 'auto', + padding: theme.spacing(1.25), // 10px padding + paddingBottom: theme.spacing(12), // 96px bottom space to clear bottom navigation + buttons + boxSizing: 'border-box', })); const CustomTabPanel = (props: TabPanelProps): JSX.Element => { diff --git a/src/pages/sidepanel/sections/Debugger.tsx b/src/pages/sidepanel/sections/Debugger.tsx index 53ab201..a12ed52 100644 --- a/src/pages/sidepanel/sections/Debugger.tsx +++ b/src/pages/sidepanel/sections/Debugger.tsx @@ -25,9 +25,9 @@ const Debugger: React.FC = ({ tagIsInstalled, tagConfig, getter, ]; return ( - + - + diff --git a/src/pages/sidepanel/sections/Personalization.tsx b/src/pages/sidepanel/sections/Personalization.tsx index c36fd96..a67e349 100644 --- a/src/pages/sidepanel/sections/Personalization.tsx +++ b/src/pages/sidepanel/sections/Personalization.tsx @@ -53,9 +53,9 @@ const Personalization: React.FC = ({ candidates, getter, s ]; return ( - + - + {candidates?.experiences.length > 0 ? ( diff --git a/src/pages/sidepanel/sections/Profile.tsx b/src/pages/sidepanel/sections/Profile.tsx index c549f1f..dd556fa 100644 --- a/src/pages/sidepanel/sections/Profile.tsx +++ b/src/pages/sidepanel/sections/Profile.tsx @@ -24,9 +24,9 @@ const Profile: React.FC = ({ profileIsLoading, profile, getter, ]; return ( - + - + {profileIsLoading ? ( diff --git a/src/pages/sidepanel/sections/ProfileDetail.tsx b/src/pages/sidepanel/sections/ProfileDetail.tsx index e9c68b9..9106eec 100644 --- a/src/pages/sidepanel/sections/ProfileDetail.tsx +++ b/src/pages/sidepanel/sections/ProfileDetail.tsx @@ -1,12 +1,64 @@ import React from 'react'; +import { Box, Button, Stack } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { FileDownload } from '@mui/icons-material'; +import { appColors } from '@root/src/theme/palette'; import TreeDisplay from '@root/src/pages/sidepanel/components/TreeDisplay'; interface ProfileDetailTabProps { profile: any; } +const StyledPanel = styled(Box)(({ theme }) => ({ + width: '100%', + backgroundColor: appColors.common.darkPanel, + borderRadius: theme.spacing(1), // 8px all corners + padding: '0.625rem', + cursor: 'default', + transition: 'none', + '&:hover': { + boxShadow: 'none', + transform: 'none', + }, +})); + +const ExportButton = styled(Button)(({ theme }) => ({ + width: '100%', + padding: theme.spacing(1.5), + backgroundColor: appColors.common.white, + color: appColors.neutral[900], + fontWeight: appColors.common.fontWeight.semiBold, + fontSize: appColors.common.fontSize.base, + textTransform: 'none', + borderRadius: theme.spacing(1), + border: `1px solid ${appColors.neutral[200]}`, + '&:hover': { + backgroundColor: appColors.neutral[50], + }, +})); + const ProfileDetail: React.FC = ({ profile }) => { - return ; + const handleExport = () => { + const dataStr = JSON.stringify(profile?.data, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `profile-${Date.now()}.json`; + link.click(); + URL.revokeObjectURL(url); + }; + + return ( + + + + + } onClick={handleExport}> + Export Profile JSON + + + ); }; export default ProfileDetail; diff --git a/src/pages/sidepanel/sections/TagConfig.test.tsx b/src/pages/sidepanel/sections/TagConfig.test.tsx new file mode 100644 index 0000000..35cf214 --- /dev/null +++ b/src/pages/sidepanel/sections/TagConfig.test.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import TagConfig from './TagConfig'; +import { ThemeProvider } from '@mui/material/styles'; +import { appTheme } from '@root/src/theme'; +import { TagConfigModel } from '@root/src/shared/models/tagConfigModel'; + +// Mock TreeDisplay component +vi.mock('@root/src/pages/sidepanel/components/TreeDisplay', () => ({ + default: ({ data }: { data: any }) =>
{JSON.stringify(data)}
, +})); + +describe('TagConfig', () => { + const mockTagConfig: TagConfigModel = { + stream: 'test-stream', + cid: ['test-cid-456'], // cid is string[] according to model + version: '3.5.0', + url: 'https://example.com', + src: 'https://c.lytics.io/api/tag/test-account-123/latest.min.js', + }; + + const renderWithTheme = (component: React.ReactElement) => { + return render({component}); + }; + + it('should render the TagConfig component', () => { + renderWithTheme(); + + const treeDisplay = screen.getByTestId('tree-display'); + expect(treeDisplay).toBeInTheDocument(); + }); + + it('should pass tagConfig data to TreeDisplay', () => { + renderWithTheme(); + + const treeDisplay = screen.getByTestId('tree-display'); + expect(treeDisplay).toHaveTextContent(mockTagConfig.stream); + expect(treeDisplay).toHaveTextContent(mockTagConfig.version); + expect(treeDisplay).toHaveTextContent(mockTagConfig.url); + }); + + it('should handle empty tagConfig', () => { + const emptyConfig = {} as TagConfigModel; + renderWithTheme(); + + const treeDisplay = screen.getByTestId('tree-display'); + expect(treeDisplay).toBeInTheDocument(); + }); + + it('should handle different tagConfig versions', () => { + const legacyConfig: TagConfigModel = { + ...mockTagConfig, + version: '2.8.0', + }; + + renderWithTheme(); + + const treeDisplay = screen.getByTestId('tree-display'); + expect(treeDisplay).toHaveTextContent('2.8.0'); + }); + + it('should handle tagConfig with missing optional fields', () => { + const partialConfig = { + stream: 'test-stream', + version: '3.5.0', + } as TagConfigModel; + + renderWithTheme(); + + const treeDisplay = screen.getByTestId('tree-display'); + expect(treeDisplay).toBeInTheDocument(); + expect(treeDisplay).toHaveTextContent('test-stream'); + }); + + it('should render TreeDisplay inside styled panel', () => { + const { container } = renderWithTheme(); + + const panel = container.querySelector('.MuiBox-root'); + const treeDisplay = screen.getByTestId('tree-display'); + + expect(panel).toContainElement(treeDisplay); + }); +}); diff --git a/src/pages/sidepanel/sections/TagConfig.tsx b/src/pages/sidepanel/sections/TagConfig.tsx index dbbebe2..b50758a 100644 --- a/src/pages/sidepanel/sections/TagConfig.tsx +++ b/src/pages/sidepanel/sections/TagConfig.tsx @@ -1,4 +1,7 @@ import React from 'react'; +import { Box } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { appColors } from '@root/src/theme/palette'; import { TagConfigModel } from '@root/src/shared/models/tagConfigModel'; import TreeDisplay from '@root/src/pages/sidepanel/components/TreeDisplay'; @@ -6,8 +9,25 @@ interface ConfigTabProps { tagConfig: TagConfigModel; } +const StyledPanel = styled(Box)(({ theme }) => ({ + width: '100%', + backgroundColor: appColors.common.darkPanel, + borderRadius: theme.spacing(1), // 8px all corners + padding: '0.625rem', // 10px inner padding + cursor: 'default', + transition: 'none', + '&:hover': { + boxShadow: 'none', + transform: 'none', + }, +})); + const TagConfig: React.FC = ({ tagConfig }) => { - return ; + return ( + + + + ); }; export default TagConfig; diff --git a/src/pages/sidepanel/sections/TagStatus.tsx b/src/pages/sidepanel/sections/TagStatus.tsx index da09e6d..21d3378 100644 --- a/src/pages/sidepanel/sections/TagStatus.tsx +++ b/src/pages/sidepanel/sections/TagStatus.tsx @@ -16,6 +16,11 @@ interface TagStatusProps { const StyledContainer = styled(Box)(({ theme }) => ({ paddingInline: theme.spacing(1.25), + paddingBottom: theme.spacing(12), // 96px bottom space to clear bottom navigation + height: '100%', + width: '100%', + overflow: 'auto', + boxSizing: 'border-box', cursor: 'default', transition: 'none', '&:hover': { diff --git a/src/theme/palette.ts b/src/theme/palette.ts index bb4fae0..a708037 100644 --- a/src/theme/palette.ts +++ b/src/theme/palette.ts @@ -43,6 +43,7 @@ const appColors = { // Common Design Tokens common: { white: '#ffffff', // Pure white background + darkPanel: '#272728', // Dark panel background fontFamily: 'SF Pro, -apple-system, BlinkMacSystemFont, sans-serif', fontSize: { base: '1rem', // Standard font size (16px)