Compare commits
3 Commits
main
...
fix-delete-modal
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eb4a1e9e3 | |||
| d8b6bcc9eb | |||
| 418bed945a |
@@ -268,7 +268,7 @@ class Base(Configuration):
|
||||
EMAIL_PORT = values.PositiveIntegerValue(None)
|
||||
EMAIL_USE_TLS = values.BooleanValue(False)
|
||||
EMAIL_USE_SSL = values.BooleanValue(False)
|
||||
EMAIL_FROM = values.Value("from@example.com")
|
||||
EMAIL_FROM = values.Value("do-not-reply@desk.beta.numerique.gouv.fr")
|
||||
|
||||
AUTH_USER_MODEL = "core.User"
|
||||
INVITATION_VALIDITY_DURATION = 604800 # 7 days, in seconds
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
||||
import { Box, BoxType } from '.';
|
||||
import { BoxType } from '.';
|
||||
|
||||
export const Card = ({
|
||||
children,
|
||||
$css,
|
||||
...props
|
||||
}: PropsWithChildren<BoxType>) => {
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
background-color: white;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 2px 2px 5px var(--shadow-color) 88;
|
||||
border: 1px solid var(--border-color);
|
||||
`;
|
||||
|
||||
export const Card = ({ children, ...props }: PropsWithChildren<BoxType>) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
$background="white"
|
||||
$radius="4px"
|
||||
$css={`
|
||||
box-shadow: 2px 2px 5px ${colorsTokens()['primary-300']}88;
|
||||
border: 1px solid ${colorsTokens()['card-border']};
|
||||
${$css}
|
||||
`}
|
||||
<Wrapper
|
||||
style={{
|
||||
'--border-color': colorsTokens()['card-border'],
|
||||
'--shadow-color': colorsTokens()['primary-300'],
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { HEADER_HEIGHT, Header } from '@/features/header';
|
||||
import { Menu } from '@/features/menu';
|
||||
import { MENU_WIDTH, Menu } from '@/features/menu';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const Main = styled.main`
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-basis: 100%;
|
||||
height: calc(100vh - ${HEADER_HEIGHT});
|
||||
max-width: calc(100% - ${MENU_WIDTH});
|
||||
`;
|
||||
|
||||
export function MainLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Box $height="100vh" $css="overflow:hidden;">
|
||||
<Box>
|
||||
<Header />
|
||||
<Box $css="flex: 1;" $direction="row">
|
||||
<Container>
|
||||
<Menu />
|
||||
<Box as="main" $height={`calc(100vh - ${HEADER_HEIGHT})`} $width="100%">
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
<Main>{children}</Main>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -110,9 +110,7 @@ export const SearchMembers = ({
|
||||
onInputChange={onInputChangeHandle}
|
||||
inputValue={input}
|
||||
placeholder={t('Search new members (name or email)')}
|
||||
noOptionsMessage={() =>
|
||||
t('Invite new members to {{teamName}}', { teamName: team.name })
|
||||
}
|
||||
noOptionsMessage={() => t('Invite new members to your team')}
|
||||
onChange={(value) => {
|
||||
setInput('');
|
||||
setUserQuery('');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Button,
|
||||
DataGrid,
|
||||
Input,
|
||||
SortModel,
|
||||
usePagination,
|
||||
} from '@openfun/cunningham-react';
|
||||
@@ -17,6 +18,7 @@ import { useTeamAccesses } from '../api/';
|
||||
import { PAGE_SIZE } from '../conf';
|
||||
|
||||
import { MemberAction } from './MemberAction';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface MemberGridProps {
|
||||
team: Team;
|
||||
@@ -50,6 +52,37 @@ function formatSortModel(
|
||||
return sort === 'desc' ? `-${orderingField}` : orderingField;
|
||||
}
|
||||
|
||||
const Wrapper = styled(Card)`
|
||||
.c__pagination {
|
||||
width: fit-content;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.c__pagination__goto {
|
||||
display: none;
|
||||
}
|
||||
table th:first-child,
|
||||
table td:first-child {
|
||||
padding-right: 0;
|
||||
width: 3.5rem;
|
||||
}
|
||||
table td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
const Actions = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-bottom: 1.25rem;
|
||||
|
||||
.filter-container {
|
||||
width: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const MemberGrid = ({ team, currentRole }: MemberGridProps) => {
|
||||
const [isModalMemberOpen, setIsModalMemberOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
@@ -96,7 +129,14 @@ export const MemberGrid = ({ team, currentRole }: MemberGridProps) => {
|
||||
return (
|
||||
<>
|
||||
{currentRole !== Role.MEMBER && (
|
||||
<Box className="m-b mb-s" $align="flex-end">
|
||||
<Actions>
|
||||
<div className="filter-container">
|
||||
<Input
|
||||
rightIcon={<span className="material-icons">search</span>}
|
||||
label="Filtrer la liste des membres"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
aria-label={t('Add members to the team')}
|
||||
style={{
|
||||
@@ -108,29 +148,10 @@ export const MemberGrid = ({ team, currentRole }: MemberGridProps) => {
|
||||
>
|
||||
{t('Add')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Actions>
|
||||
)}
|
||||
<Card
|
||||
className="m-b pb-s"
|
||||
$overflow="auto"
|
||||
$css={`
|
||||
margin-top:0;
|
||||
& .c__pagination__goto {
|
||||
display: none;
|
||||
}
|
||||
& table th:first-child,
|
||||
& table td:first-child {
|
||||
padding-right: 0;
|
||||
width: 3.5rem;
|
||||
}
|
||||
& table td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
`}
|
||||
aria-label={t('List members card')}
|
||||
>
|
||||
<Wrapper aria-label={t('List members card')}>
|
||||
{error && <TextErrors causes={error.cause} />}
|
||||
|
||||
<DataGrid
|
||||
columns={[
|
||||
{
|
||||
@@ -179,7 +200,7 @@ export const MemberGrid = ({ team, currentRole }: MemberGridProps) => {
|
||||
onSortModelChange={setSortModel}
|
||||
sortModel={sortModel}
|
||||
/>
|
||||
</Card>
|
||||
</Wrapper>
|
||||
{isModalMemberOpen && (
|
||||
<ModalAddMembers
|
||||
currentRole={currentRole}
|
||||
|
||||
@@ -128,7 +128,7 @@ export const ModalDelete = ({ access, onClose, team }: ModalDeleteProps) => {
|
||||
$theme="primary"
|
||||
>
|
||||
<IconUser width={20} height={20} />
|
||||
<Text>{access.user.name}</Text>
|
||||
<Text>{access.user.name || access.user.email}</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import IconGroup from '@/assets/icons/icon-group.svg';
|
||||
import { Box } from '@/components/';
|
||||
import useCunninghamTheme from '@/cunningham/useCunninghamTheme';
|
||||
|
||||
import MenuItem from './MenuItems';
|
||||
@@ -11,25 +10,34 @@ import IconContacts from './assets/icon-contacts.svg';
|
||||
import IconSearch from './assets/icon-search.svg';
|
||||
import IconFavorite from './assets/icon-stars.svg';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const MENU_WIDTH = '62px';
|
||||
|
||||
const Wrapper = styled.menu`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--bg-color);
|
||||
width: ${MENU_WIDTH};
|
||||
gap: 0.8rem;
|
||||
`;
|
||||
|
||||
export const Menu = () => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="menu"
|
||||
className="m-0 p-0"
|
||||
$background={colorsTokens()['primary-800']}
|
||||
$height="100%"
|
||||
$justify="space-between"
|
||||
<Wrapper
|
||||
className="m-0 p-0 pt-l"
|
||||
style={{
|
||||
'--bg-color': colorsTokens()['primary-800'],
|
||||
}}
|
||||
>
|
||||
<Box className="pt-l" $direction="column" $gap="0.8rem">
|
||||
<MenuItem Icon={IconSearch} label={t('Search')} href="/" />
|
||||
<MenuItem Icon={IconFavorite} label={t('Favorite')} href="/favorite" />
|
||||
<MenuItem Icon={IconRecent} label={t('Recent')} href="/recent" />
|
||||
<MenuItem Icon={IconContacts} label={t('Contacts')} href="/contacts" />
|
||||
<MenuItem Icon={IconGroup} label={t('Groups')} href="/groups" />
|
||||
</Box>
|
||||
</Box>
|
||||
<MenuItem Icon={IconSearch} label={t('Search')} href="/" />
|
||||
<MenuItem Icon={IconFavorite} label={t('Favorite')} href="/favorite" />
|
||||
<MenuItem Icon={IconRecent} label={t('Recent')} href="/recent" />
|
||||
<MenuItem Icon={IconContacts} label={t('Contacts')} href="/contacts" />
|
||||
<MenuItem Icon={IconGroup} label={t('Groups')} href="/groups" />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Button } from '@openfun/cunningham-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import IconGroup from '@/assets/icons/icon-group2.svg';
|
||||
import { Box, Card, StyledLink, Text } from '@/components';
|
||||
@@ -11,6 +12,18 @@ import { useCreateTeam } from '../api';
|
||||
|
||||
import { InputTeamName } from './InputTeamName';
|
||||
|
||||
const Wrapper = styled(Card)`
|
||||
height: 70%;
|
||||
padding: 2.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 24rem;
|
||||
min-width: 22rem;
|
||||
margin: auto;
|
||||
`;
|
||||
|
||||
export const CardCreateTeam = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
@@ -28,15 +41,7 @@ export const CardCreateTeam = () => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="p-b"
|
||||
$height="70%"
|
||||
$justify="space-between"
|
||||
$width="100%"
|
||||
$maxWidth="24rem"
|
||||
$minWidth="22rem"
|
||||
aria-label={t('Create new team card')}
|
||||
>
|
||||
<Wrapper aria-label={t('Create new team card')}>
|
||||
<Box $gap="1rem">
|
||||
<Box $align="center">
|
||||
<IconGroup
|
||||
@@ -64,6 +69,6 @@ export const CardCreateTeam = () => {
|
||||
{t('Create the team')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -60,11 +60,12 @@ export const Panel = () => {
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
className="pr-l pl-s pt-s pb-s"
|
||||
className="pl-s pt-s pb-s"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
$css={`
|
||||
padding-right: 2.4rem;
|
||||
border-bottom: 1px solid ${colorsTokens()['primary-300']};
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -63,7 +63,12 @@ export const TeamItem = ({ team }: TeamProps) => {
|
||||
`}
|
||||
>
|
||||
<StyledLink className="p-s pt-t pb-t" href={`/teams/${team.id}`}>
|
||||
<Box $align="center" $direction="row" $gap="0.5rem">
|
||||
<Box
|
||||
$align="center"
|
||||
$direction="row"
|
||||
$gap="0.5rem"
|
||||
$css="min-width: 0;"
|
||||
>
|
||||
{hasMembers ? (
|
||||
<IconGroup
|
||||
aria-label={t(`Teams icon`)}
|
||||
@@ -89,7 +94,10 @@ export const TeamItem = ({ team }: TeamProps) => {
|
||||
$weight="bold"
|
||||
$color={!hasMembers ? colorsTokens()['greyscale-600'] : undefined}
|
||||
$css={`
|
||||
min-width: 14rem;
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
`}
|
||||
>
|
||||
{team.name}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Role, Team } from '../types';
|
||||
|
||||
import { ModalUpdateTeam } from './ModalUpdateTeam';
|
||||
import { TeamActions } from './TeamActions';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const format: DateTimeFormatOptions = {
|
||||
month: '2-digit',
|
||||
@@ -17,6 +18,71 @@ const format: DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
};
|
||||
|
||||
const Wrapper = styled(Card)`
|
||||
min-height: fit-content;
|
||||
|
||||
.actions-container {
|
||||
align-self: flex-end;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
margin-bottom: 3rem;
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
gap: 2rem;
|
||||
|
||||
margin: 3rem 1.25rem 2rem 3rem;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
|
||||
p,
|
||||
h3 {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.625rem;
|
||||
font-size: 0.81rem;
|
||||
color: #303c4b;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const SubHeader = styled.div`
|
||||
gap: 1rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 1rem 0 1.5rem 8rem;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
|
||||
p {
|
||||
display: inline;
|
||||
font-size: 0.81rem;
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
interface TeamInfoProps {
|
||||
team: Team;
|
||||
currentRole: Role;
|
||||
@@ -38,13 +104,13 @@ export const TeamInfo = ({ team, currentRole }: TeamInfoProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="m-b" style={{ paddingBottom: 0 }}>
|
||||
<Box $css="align-self: flex-end;" className="m-t" $position="absolute">
|
||||
<Wrapper>
|
||||
<div className="actions-container m-t">
|
||||
<TeamActions currentRole={currentRole} team={team} />
|
||||
</Box>
|
||||
<Box className="m-b" $direction="row" $align="center" $gap="1.5rem">
|
||||
</div>
|
||||
<Header>
|
||||
<IconGroup
|
||||
width={44}
|
||||
width={48}
|
||||
color={colorsTokens()['primary-text']}
|
||||
aria-label={t('icon group')}
|
||||
style={{
|
||||
@@ -52,46 +118,35 @@ export const TeamInfo = ({ team, currentRole }: TeamInfoProps) => {
|
||||
alignSelf: 'start',
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Text as="h3" $weight="bold" $size="1.25rem" className="mt-0">
|
||||
<div>
|
||||
<h3>
|
||||
{t('Members of “{{teamName}}“', {
|
||||
teamName: team.name,
|
||||
})}
|
||||
</Text>
|
||||
<Text $size="m">
|
||||
</h3>
|
||||
<p>
|
||||
{t('Add people to the “{{teamName}}“ group.', {
|
||||
teamName: team.name,
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
className="p-s"
|
||||
$gap="3rem"
|
||||
$direction="row"
|
||||
$justify="start"
|
||||
$css={`
|
||||
border-top: 1px solid ${colorsTokens()['card-border']};
|
||||
padding-left: 1.5rem;
|
||||
`}
|
||||
</p>
|
||||
</div>
|
||||
</Header>
|
||||
<SubHeader
|
||||
style={{
|
||||
'--border-color': colorsTokens()['card-border'],
|
||||
}}
|
||||
>
|
||||
<Text $size="s" as="p">
|
||||
{t('{{count}} member', { count: team.accesses.length })}
|
||||
</Text>
|
||||
<Text $size="s" $display="inline" as="p">
|
||||
<p>{t('{{count}} member', { count: team.accesses.length })}</p>
|
||||
<p>
|
||||
{t('Created at')}
|
||||
<Text $weight="bold" $display="inline">
|
||||
{created_at}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text $size="s" $display="inline" as="p">
|
||||
<strong>{created_at}</strong>
|
||||
</p>
|
||||
<p>
|
||||
{t('Last update at')}
|
||||
<Text $weight="bold" $display="inline">
|
||||
{updated_at}
|
||||
</Text>
|
||||
</Text>
|
||||
</Box>
|
||||
</Card>
|
||||
<strong>{updated_at}</strong>
|
||||
</p>
|
||||
</SubHeader>
|
||||
</Wrapper>
|
||||
{isModalUpdateOpen && (
|
||||
<ModalUpdateTeam
|
||||
onClose={() => setIsModalUpdateOpen(false)}
|
||||
|
||||
@@ -1,25 +1,34 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { MainLayout } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Panel } from '@/features/teams';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--bg-color);
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 1.85rem;
|
||||
`;
|
||||
|
||||
export function TeamLayout({ children }: PropsWithChildren) {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Box $height="inherit" $direction="row">
|
||||
<Panel />
|
||||
<Box
|
||||
$background={colorsTokens()['primary-bg']}
|
||||
$width="100%"
|
||||
$height="inherit"
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
<Panel />
|
||||
<Container
|
||||
style={{
|
||||
'--bg-color': colorsTokens()['primary-bg'],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Container>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"Freedom Equality Fraternity Logo": "Logo Liberté Égalité Fraternité",
|
||||
"Groups": "Groupes",
|
||||
"Invitation sent to {{email}}": "Invitation envoyée à {{email}}",
|
||||
"Invite new members to {{teamName}}": "Invitez de nouveaux membres à rejoindre {{teamName}}",
|
||||
"Invite new members to your team": "Invitez de nouveaux membres à rejoindre votre groupe",
|
||||
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Il semble que la page que vous cherchez n'existe pas ou ne puisse pas être affichée correctement.",
|
||||
"Language": "Langue",
|
||||
"Language Icon": "Icône de langue",
|
||||
|
||||
@@ -3,10 +3,18 @@ import type { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Box, StyledLink } from '@/components';
|
||||
import { StyledLink } from '@/components';
|
||||
import { TeamLayout } from '@/features/teams/';
|
||||
import { NextPageWithLayout } from '@/types/next';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
width: fit-content;
|
||||
`;
|
||||
@@ -15,11 +23,11 @@ const Page: NextPageWithLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box $align="center" $justify="center" $height="inherit">
|
||||
<Container>
|
||||
<StyledLink href="/teams/create">
|
||||
<StyledButton>{t('Create a new team')}</StyledButton>
|
||||
</StyledLink>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user