Skip to content

Commit

Permalink
Passwords implementation (#32)
Browse files Browse the repository at this point in the history
* Added DataTable for passwords.

* Added Password Page.

---------

Co-authored-by: Pavel <[email protected]>
  • Loading branch information
PavelAl and Pavel authored Apr 21, 2023
1 parent d458b81 commit 19eba78
Show file tree
Hide file tree
Showing 43 changed files with 1,752 additions and 1,017 deletions.
3 changes: 2 additions & 1 deletion code/frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ module.exports = {
order: 'asc',
caseInsensitive: true
}
}]
}],
'no-console': 'error'
},
overrides: [{
files: ['*.stories.tsx'],
Expand Down
16 changes: 8 additions & 8 deletions code/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
"react-icons": "^4.8.0"
},
"devDependencies": {
"@storybook/addon-essentials": "7.0.0-rc.1",
"@storybook/addon-interactions": "7.0.0-rc.1",
"@storybook/addon-links": "7.0.0-rc.1",
"@storybook/blocks": "7.0.0-rc.1",
"@storybook/react": "7.0.0-rc.1",
"@storybook/react-vite": "7.0.0-rc.1",
"@storybook/testing-library": "0.0.14-next.1",
"@storybook/addon-essentials": "^7.0.2",
"@storybook/addon-interactions": "^7.0.2",
"@storybook/addon-links": "^7.0.2",
"@storybook/blocks": "^7.0.2",
"@storybook/react": "^7.0.2",
"@storybook/react-vite": "^7.0.2",
"@storybook/testing-library": "^0.1.0",
"@types/eslint": "^8.21.1",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
Expand All @@ -52,7 +52,7 @@
"eslint-plugin-storybook": "^0.6.11",
"prettier": "^2.8.4",
"sass": "^1.59.2",
"storybook": "7.0.0-rc.1",
"storybook": "7.0.2",
"typescript": "^4.9.3",
"vite": "^4.1.0",
"vite-plugin-sass-dts": "^1.3.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Checkbox, Stack } from '@chakra-ui/react';
import { FC } from 'react';

import { useLocale } from '~/locale';

import { CheckboxGroupProps } from './CheckboxGroup.types';

export const CheckboxGroup: FC<CheckboxGroupProps> = props => {
const { options, selectedKeys, onChange, onToggleAll } = props;
const locale = useLocale();

return (
<Stack spacing={[1, 5]}>
<Checkbox
isChecked={selectedKeys.length === options.length}
isIndeterminate={selectedKeys.length > 0 && selectedKeys.length !== options.length}
onChange={onToggleAll}
>
{locale.checkboxes.selectAll}
</Checkbox>

{options.map(({ key, label }) => (
<Checkbox key={key} isChecked={selectedKeys.indexOf(key) > -1} onChange={() => onChange(key)}>
{label}
</Checkbox>
))}
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Option } from '~/Common/types';

export interface CheckboxGroupProps {
options: Option[];
selectedKeys: string[];
onChange: (key: string) => void;
onToggleAll: () => void;
}
2 changes: 2 additions & 0 deletions code/frontend/src/Common/components/CheckboxGroup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './CheckboxGroup';
export * from './CheckboxGroup.types';
47 changes: 47 additions & 0 deletions code/frontend/src/Common/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Table, Thead, Tbody, Tr } from '@chakra-ui/react';
import { useEffect, useState } from 'react';

import { HeaderCell, TableCell } from './components';
import { DataTableProps, FilterSettings } from './DataTable.types';

export function DataTable<Data extends object>(props: DataTableProps<Data>) {
const { data, columns, getRowKey, onFilterChange } = props;

const [filterSettings, setFilterSettings] = useState<FilterSettings>({});

useEffect(() => {
if (onFilterChange) onFilterChange(filterSettings);
}, [filterSettings]);

return (
<Table>
<Thead>
<Tr>
{columns.map(column => (
<HeaderCell
key={column.name}
column={column}
onFilterChange={selectedKeys =>
setFilterSettings(prevState => ({ ...prevState, [column.name]: selectedKeys }))
}
/>
))}
</Tr>
</Thead>

<Tbody>
{data.map(row => {
const rowKey = getRowKey(row);

return (
<Tr key={rowKey}>
{columns.map(({ name, getRowValue }) => (
<TableCell key={`${rowKey}-${name}`} value={getRowValue(row)} />
))}
</Tr>
);
})}
</Tbody>
</Table>
);
}
19 changes: 19 additions & 0 deletions code/frontend/src/Common/components/DataTable/DataTable.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Option } from '~/Common/types';

export type Column<DataType extends object> = {
name: string;
title: string;
filterOptions?: Option[];
getRowValue: (row: DataType) => string;
};

export type FilterSettings = {
[name: string]: string[];
};

export type DataTableProps<DataType extends object> = {
data: DataType[];
columns: Column<DataType>[];
getRowKey: (row: DataType) => string;
onFilterChange?: (filterSettings: FilterSettings) => void;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Popover, Th, Flex, PopoverTrigger, IconButton, Portal, PopoverContent, PopoverBody } from '@chakra-ui/react';
import { FilterList } from '@mui/icons-material';
import { useEffect } from 'react';

import { CheckboxGroup } from '../../CheckboxGroup';
import { Column } from '../DataTable.types';

import { useCheckboxGroupKeys } from '~/Common/hooks';

interface HeaderCellProps<Data extends object> {
column: Column<Data>;
onFilterChange: (selectedKeys: string[]) => void;
}

export function HeaderCell<Data extends object>({ column, onFilterChange }: HeaderCellProps<Data>) {
const { title, filterOptions = [] } = column;
const { selectedKeys, onChange, onToggleAll } = useCheckboxGroupKeys(filterOptions);

useEffect(() => {
if (filterOptions.length > 0) onFilterChange(selectedKeys);
}, [filterOptions, selectedKeys]);

return (
<Popover>
<Th>
<Flex justifyContent={'space-between'} alignItems={'center'}>
{title}

{filterOptions.length > 0 && (
<PopoverTrigger>
<IconButton colorScheme="blue" aria-label="Search database" icon={<FilterList />} />
</PopoverTrigger>
)}
</Flex>

<Portal>
<PopoverContent minWidth={'3xs'} width={'auto'}>
<PopoverBody padding={'12px 16px'}>
<CheckboxGroup
options={filterOptions}
selectedKeys={selectedKeys}
onChange={onChange}
onToggleAll={onToggleAll}
/>
</PopoverBody>
</PopoverContent>
</Portal>
</Th>
</Popover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Td } from '@chakra-ui/react';

interface TableCellProps {
value: string;
}

export function TableCell({ value }: TableCellProps) {
return <Td>{value}</Td>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './HeaderCell';
export * from './TableCell';
2 changes: 2 additions & 0 deletions code/frontend/src/Common/components/DataTable/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './DataTable';
export * from './DataTable.types';
2 changes: 2 additions & 0 deletions code/frontend/src/Common/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './CheckboxGroup';
export * from './DataTable';
1 change: 1 addition & 0 deletions code/frontend/src/Common/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useCheckboxGroupKeys';
18 changes: 18 additions & 0 deletions code/frontend/src/Common/hooks/useCheckboxGroupKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from 'react';

import { Option } from '../types';

export const useCheckboxGroupKeys = (options: Option[]) => {
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);

const onChange = (key: string) => {
setSelectedKeys(prevState => (prevState.includes(key) ? prevState.filter(k => k !== key) : [...prevState, key]));
};
const onToggleAll = () => {
setSelectedKeys(prevState => {
return prevState.length === 0 ? options.map(({ key }) => key) : [];
});
};

return { selectedKeys, onChange, onToggleAll };
};
3 changes: 3 additions & 0 deletions code/frontend/src/Common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './components';
export * from './hooks';
export * from './types';
26 changes: 26 additions & 0 deletions code/frontend/src/Common/stories/CheckboxGroup.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meta, StoryFn } from '@storybook/react';

import { CheckboxGroup } from '../components';
import { useCheckboxGroupKeys } from '../hooks';

import { patternFilterOptions } from '~/Passwords/data';

export default {
title: 'Common/components/CheckboxGroup'
} as Meta;

export const Default: StoryFn = () => {
const { selectedKeys, onChange, onToggleAll } = useCheckboxGroupKeys(patternFilterOptions);

return (
<CheckboxGroup
options={patternFilterOptions}
selectedKeys={selectedKeys}
onChange={onChange}
onToggleAll={onToggleAll}
/>
);
};

Default.args = {};
Default.storyName = 'CheckboxGroup';
54 changes: 54 additions & 0 deletions code/frontend/src/Common/stories/DataTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Meta, StoryFn } from '@storybook/react';

import { Column, DataTable } from '../components/DataTable';

import { patternFilterOptions } from '~/Passwords/data';

export default {
title: 'Common/components/DataTable'
} as Meta;

type UnitConversion = {
key: string;
fromUnit: string;
toUnit: string;
factor: number;
};

const data: UnitConversion[] = [
{
key: 'inches',
fromUnit: 'inches',
toUnit: 'millimetres (mm)',
factor: 25.4
},
{
key: 'feet',
fromUnit: 'feet',
toUnit: 'centimetres (cm)',
factor: 30.48
},
{
key: 'yards',
fromUnit: 'yards',
toUnit: 'metres (m)',
factor: 0.91444
}
];

const columns: Column<UnitConversion>[] = [
{ name: 'fromUnit', title: 'To convert', getRowValue: row => row.fromUnit },
{
name: 'toUnit',
title: 'Into',
filterOptions: patternFilterOptions,
getRowValue: row => row.toUnit
},
{ name: 'factor', title: 'Multiply by', getRowValue: row => row.factor.toString() }
];

export const Default: StoryFn = () => {
return <DataTable columns={columns} data={data} getRowKey={row => row.key} />;
};

Default.storyName = 'DataTable';
4 changes: 4 additions & 0 deletions code/frontend/src/Common/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Option {
key: string;
label: string;
}
22 changes: 4 additions & 18 deletions code/frontend/src/Info/components/Introduction/Introduction.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Button, Flex, HStack, Image, Stack, Text } from '@chakra-ui/react';
import { Button, HStack, Image, Stack, Text } from '@chakra-ui/react';

import { ServerlessPassTitle } from '../ServerlessPassTitle';

import Logo from '~/assets/logo.png';
import { useLocale } from '~/locale';
Expand All @@ -7,7 +9,7 @@ export const Introduction: React.FC = () => {
return (
<HStack spacing={'80px'}>
<Stack maxWidth={{ sm: '100%', md: 550 }} spacing={'24px'}>
<Title />
<ServerlessPassTitle />

<Description />

Expand All @@ -19,22 +21,6 @@ export const Introduction: React.FC = () => {
);
};

const Title: React.FC = () => {
const { root } = useLocale();

return (
<Flex alignItems={'flex-start'}>
<Text fontSize={56} fontWeight={'bold'}>
{root.header.title}
</Text>

<Text fontSize={'sm'} fontWeight={'bold'}>
{root.header.tm}
</Text>
</Flex>
);
};

const Description: React.FC = () => {
const { root } = useLocale();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Flex, Text } from '@chakra-ui/react';

import { useLocale } from '~/locale';

export const ServerlessPassTitle: React.FC = () => {
const { root } = useLocale();

return (
<Flex alignItems={'flex-start'}>
<Text fontSize={56} fontWeight={'bold'}>
{root.header.title}
</Text>

<Text fontSize={'sm'} fontWeight={'bold'}>
{root.header.tm}
</Text>
</Flex>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ServerlessPassTitle';
Loading

0 comments on commit 19eba78

Please sign in to comment.