Skip to content
Merged
10 changes: 9 additions & 1 deletion src/pages/sidepanel/SidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ const SidePanel: React.FC<SidePanelProps> = ({ key, isEnabled }) => {
</Box>
) : (
<>
<Box justifyContent={'center'} alignItems={'flex-start'} display="flex" flexDirection="column">
<Box
justifyContent={'center'}
alignItems={'flex-start'}
display="flex"
flexDirection="column"
sx={{
height: 'calc(100vh - 160px)', // Full height minus top navigation (~90px) and bottom navigation (70px)
overflow: 'hidden',
}}>
<Routes>
<Route path="/settings" element={<Configuration />} />
<Route
Expand Down
9 changes: 7 additions & 2 deletions src/pages/sidepanel/components/BottomNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@ const StyledBottomNavigation = styled(BottomNavigation)(({ theme }) => ({
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': {
Expand Down
6 changes: 3 additions & 3 deletions src/pages/sidepanel/components/CustomTabPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/sidepanel/sections/Debugger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const Debugger: React.FC<DebuggerProps> = ({ tagIsInstalled, tagConfig, getter,
];

return (
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100%'} width={'100%'}>
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
<CustomTabPanel value={getter} index={0}>
<TagConfig tagConfig={tagConfig} />
</CustomTabPanel>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/sidepanel/sections/Personalization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ const Personalization: React.FC<PersonalizationProps> = ({ candidates, getter, s
];

return (
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100%'} width={'100%'}>
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
<CustomTabPanel value={getter} index={0}>
{candidates?.experiences.length > 0 ? (
<TabDetails items={candidates?.experiences} />
Expand Down
4 changes: 2 additions & 2 deletions src/pages/sidepanel/sections/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ const Profile: React.FC<ProfileTabProps> = ({ profileIsLoading, profile, getter,
];

return (
<Stack alignItems={'flex-start'} justifyContent={'center'} height={'100vh'} width={'100%'} overflow={'hidden'}>
<Stack alignItems={'flex-start'} justifyContent={'flex-start'} height={'100%'} width={'100%'}>
<TabNavigation tabs={tabs} value={getter} onChange={handleSetTab} />
<Box flexGrow={1} width={'100%'} overflow={'auto'}>
<Box flexGrow={1} width={'100%'} overflow={'auto'} sx={{ height: 0 }}>
<CustomTabPanel value={getter} index={0}>
{profileIsLoading ? (
<Box m={2}>
Expand Down
54 changes: 53 additions & 1 deletion src/pages/sidepanel/sections/ProfileDetail.tsx
Original file line number Diff line number Diff line change
@@ -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<ProfileDetailTabProps> = ({ profile }) => {
return <TreeDisplay data={profile?.data} collapsed={2} />;
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 (
<Stack spacing={2} width="100%">
<StyledPanel>
<TreeDisplay data={profile?.data} collapsed={2} />
</StyledPanel>
<ExportButton startIcon={<FileDownload />} onClick={handleExport}>
Export Profile JSON
</ExportButton>
</Stack>
);
};

export default ProfileDetail;
84 changes: 84 additions & 0 deletions src/pages/sidepanel/sections/TagConfig.test.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => <div data-testid="tree-display">{JSON.stringify(data)}</div>,
}));

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(<ThemeProvider theme={appTheme}>{component}</ThemeProvider>);
};

it('should render the TagConfig component', () => {
renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);

const treeDisplay = screen.getByTestId('tree-display');
expect(treeDisplay).toBeInTheDocument();
});

it('should pass tagConfig data to TreeDisplay', () => {
renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);

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(<TagConfig tagConfig={emptyConfig} />);

const treeDisplay = screen.getByTestId('tree-display');
expect(treeDisplay).toBeInTheDocument();
});

it('should handle different tagConfig versions', () => {
const legacyConfig: TagConfigModel = {
...mockTagConfig,
version: '2.8.0',
};

renderWithTheme(<TagConfig tagConfig={legacyConfig} />);

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(<TagConfig tagConfig={partialConfig} />);

const treeDisplay = screen.getByTestId('tree-display');
expect(treeDisplay).toBeInTheDocument();
expect(treeDisplay).toHaveTextContent('test-stream');
});

it('should render TreeDisplay inside styled panel', () => {
const { container } = renderWithTheme(<TagConfig tagConfig={mockTagConfig} />);

const panel = container.querySelector('.MuiBox-root');
const treeDisplay = screen.getByTestId('tree-display');

expect(panel).toContainElement(treeDisplay);
});
});
22 changes: 21 additions & 1 deletion src/pages/sidepanel/sections/TagConfig.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
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';

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<ConfigTabProps> = ({ tagConfig }) => {
return <TreeDisplay data={tagConfig} />;
return (
<StyledPanel>
<TreeDisplay data={tagConfig} />
</StyledPanel>
);
};

export default TagConfig;
5 changes: 5 additions & 0 deletions src/pages/sidepanel/sections/TagStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down
1 change: 1 addition & 0 deletions src/theme/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down