Renamed (app) folder to (main).
This commit is contained in:
67
src/app/(main)/settings/teams/[id]/TeamAddWebsiteForm.js
Normal file
67
src/app/(main)/settings/teams/[id]/TeamAddWebsiteForm.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Dropdown, Form, FormButtons, FormRow, Item, SubmitButton } from 'react-basics';
|
||||
import WebsiteTags from '../WebsiteTags';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
export function TeamAddWebsiteForm({ teamId, onSave, onClose }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, post, useQuery, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}/websites`, data));
|
||||
const { data: websites } = useQuery(['websites'], () => get('/websites'));
|
||||
const [newWebsites, setNewWebsites] = useState([]);
|
||||
const formRef = useRef();
|
||||
|
||||
const hasData = websites && websites.data.length > 0;
|
||||
|
||||
const handleSubmit = () => {
|
||||
mutate(
|
||||
{ websiteIds: newWebsites },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
onSave();
|
||||
onClose();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddWebsite = value => {
|
||||
if (!newWebsites.some(a => a === value)) {
|
||||
const nextValue = [...newWebsites];
|
||||
|
||||
nextValue.push(value);
|
||||
|
||||
setNewWebsites(nextValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveWebsite = value => {
|
||||
const newValue = newWebsites.filter(a => a !== value);
|
||||
|
||||
setNewWebsites(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasData && (
|
||||
<Form onSubmit={handleSubmit} error={error} ref={formRef}>
|
||||
<FormRow label={formatMessage(labels.websites)}>
|
||||
<Dropdown items={websites.data} onChange={handleAddWebsite} style={{ width: 300 }}>
|
||||
{({ id, name }) => <Item key={id}>{name}</Item>}
|
||||
</Dropdown>
|
||||
</FormRow>
|
||||
<WebsiteTags items={websites.data} websites={newWebsites} onClick={handleRemoveWebsite} />
|
||||
<FormButtons flex>
|
||||
<SubmitButton disabled={newWebsites && newWebsites.length === 0}>
|
||||
{formatMessage(labels.addWebsite)}
|
||||
</SubmitButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamAddWebsiteForm;
|
||||
73
src/app/(main)/settings/teams/[id]/TeamEditForm.js
Normal file
73
src/app/(main)/settings/teams/[id]/TeamEditForm.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
SubmitButton,
|
||||
Form,
|
||||
FormInput,
|
||||
FormRow,
|
||||
FormButtons,
|
||||
TextField,
|
||||
Button,
|
||||
Flexbox,
|
||||
} from 'react-basics';
|
||||
import { getRandomChars } from 'next-basics';
|
||||
import { useRef, useState } from 'react';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
|
||||
export function TeamEditForm({ teamId, data, onSave, readOnly }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation(data => post(`/teams/${teamId}`, data));
|
||||
const ref = useRef(null);
|
||||
const [accessCode, setAccessCode] = useState(data.accessCode);
|
||||
|
||||
const handleSubmit = async data => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
ref.current.reset(data);
|
||||
onSave(data);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegenerate = () => {
|
||||
const code = generateId();
|
||||
ref.current.setValue('accessCode', code, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
setAccessCode(code);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error} values={data}>
|
||||
<FormRow label={formatMessage(labels.teamId)}>
|
||||
<TextField value={teamId} readOnly allowCopy />
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.name)}>
|
||||
{!readOnly && (
|
||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField />
|
||||
</FormInput>
|
||||
)}
|
||||
{readOnly && data.name}
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.accessCode)}>
|
||||
<Flexbox gap={10}>
|
||||
<TextField value={accessCode} readOnly allowCopy />
|
||||
{!readOnly && (
|
||||
<Button onClick={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||
)}
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
{!readOnly && (
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
||||
</FormButtons>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamEditForm;
|
||||
35
src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js
Normal file
35
src/app/(main)/settings/teams/[id]/TeamMemberRemoveButton.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||
|
||||
export function TeamMemberRemoveButton({ teamId, userId, disabled, onSave }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, isLoading } = useMutation(() => del(`/teams/${teamId}/users/${userId}`));
|
||||
|
||||
const handleRemoveTeamMember = () => {
|
||||
mutate(
|
||||
{},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSave();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
onClick={() => handleRemoveTeamMember()}
|
||||
disabled={disabled}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamMemberRemoveButton;
|
||||
27
src/app/(main)/settings/teams/[id]/TeamMembers.js
Normal file
27
src/app/(main)/settings/teams/[id]/TeamMembers.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import TeamMembersTable from './TeamMembersTable';
|
||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
|
||||
export function TeamMembers({ teamId, readOnly }) {
|
||||
const { get } = useApi();
|
||||
const { getProps } = useFilterQuery(
|
||||
['team:users', teamId],
|
||||
params => {
|
||||
return get(`/teams/${teamId}/users`, {
|
||||
...params,
|
||||
});
|
||||
},
|
||||
{ enabled: !!teamId },
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable {...getProps()}>
|
||||
{({ data }) => <TeamMembersTable data={data} readOnly={readOnly} />}
|
||||
</DataTable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamMembers;
|
||||
37
src/app/(main)/settings/teams/[id]/TeamMembersTable.js
Normal file
37
src/app/(main)/settings/teams/[id]/TeamMembersTable.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import TeamMemberRemoveButton from './TeamMemberRemoveButton';
|
||||
|
||||
export function TeamMembersTable({ data = [], teamId, readOnly, onChange }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
const roles = {
|
||||
[ROLES.teamOwner]: formatMessage(labels.teamOwner),
|
||||
[ROLES.teamMember]: formatMessage(labels.teamMember),
|
||||
};
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="username" label={formatMessage(labels.username)} />
|
||||
<GridColumn name="role" label={formatMessage(labels.role)}>
|
||||
{row => roles[row?.teamUser?.[0]?.role]}
|
||||
</GridColumn>
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
return (
|
||||
!readOnly &&
|
||||
row?.teamUser?.[0]?.role !== ROLES.teamOwner &&
|
||||
user?.id !== row?.id && (
|
||||
<TeamMemberRemoveButton teamId={teamId} userId={row.id} onSave={onChange} />
|
||||
)
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamMembersTable;
|
||||
65
src/app/(main)/settings/teams/[id]/TeamSettings.js
Normal file
65
src/app/(main)/settings/teams/[id]/TeamSettings.js
Normal file
@@ -0,0 +1,65 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Item, Loading, Tabs, useToasts, Flexbox } from 'react-basics';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import TeamEditForm from './TeamEditForm';
|
||||
import TeamMembers from './TeamMembers';
|
||||
import TeamWebsites from './TeamWebsites';
|
||||
|
||||
export function TeamSettings({ teamId }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
const [values, setValues] = useState(null);
|
||||
const [tab, setTab] = useState('details');
|
||||
const { get, useQuery } = useApi();
|
||||
const { showToast } = useToasts();
|
||||
const { data, isLoading } = useQuery(
|
||||
['team', teamId],
|
||||
() => {
|
||||
if (teamId) {
|
||||
return get(`/teams/${teamId}`);
|
||||
}
|
||||
},
|
||||
{ cacheTime: 0 },
|
||||
);
|
||||
const canEdit = data?.teamUser?.find(
|
||||
({ userId, role }) => role === ROLES.teamOwner && userId === user.id,
|
||||
);
|
||||
|
||||
const handleSave = data => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
setValues(state => ({ ...state, ...data }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setValues(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (isLoading || !values) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox direction="column">
|
||||
<PageHeader title={values?.name} />
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
|
||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||
<Item key="members">{formatMessage(labels.members)}</Item>
|
||||
<Item key="websites">{formatMessage(labels.websites)}</Item>
|
||||
</Tabs>
|
||||
{tab === 'details' && (
|
||||
<TeamEditForm teamId={teamId} data={values} onSave={handleSave} readOnly={!canEdit} />
|
||||
)}
|
||||
{tab === 'members' && <TeamMembers teamId={teamId} readOnly={!canEdit} />}
|
||||
{tab === 'websites' && <TeamWebsites teamId={teamId} readOnly={!canEdit} />}
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamSettings;
|
||||
50
src/app/(main)/settings/teams/[id]/TeamWebsites.js
Normal file
50
src/app/(main)/settings/teams/[id]/TeamWebsites.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ActionForm, Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import TeamWebsitesTable from './TeamWebsitesTable';
|
||||
import TeamAddWebsiteForm from './TeamAddWebsiteForm';
|
||||
import useApi from 'components/hooks/useApi';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
|
||||
export function TeamWebsites({ teamId }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { user } = useUser();
|
||||
const { get } = useApi();
|
||||
const { getProps, refetch } = useFilterQuery(
|
||||
['team:websites', teamId],
|
||||
params => {
|
||||
return get(`/teams/${teamId}/websites`, {
|
||||
...params,
|
||||
});
|
||||
},
|
||||
{ enabled: !!user },
|
||||
);
|
||||
|
||||
const handleWebsiteAdd = () => {
|
||||
refetch();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionForm description={formatMessage(messages.teamWebsitesInfo)}>
|
||||
<ModalTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addWebsite)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.addWebsite)}>
|
||||
{close => (
|
||||
<TeamAddWebsiteForm teamId={teamId} onSave={handleWebsiteAdd} onClose={close} />
|
||||
)}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</ActionForm>
|
||||
<DataTable {...getProps()}>{({ data }) => <TeamWebsitesTable data={data} />}</DataTable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamWebsites;
|
||||
42
src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js
Normal file
42
src/app/(main)/settings/teams/[id]/TeamWebsitesTable.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import useUser from 'components/hooks/useUser';
|
||||
import Link from 'next/link';
|
||||
import { Button, GridColumn, GridTable, Icon, Icons, Text } from 'react-basics';
|
||||
import TeamWebsiteRemoveButton from '../TeamWebsiteRemoveButton';
|
||||
|
||||
export function TeamWebsitesTable({ data = [], onSave }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useUser();
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="domain" label={formatMessage(labels.domain)} />
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
const { id: teamId, teamUser } = row.teamWebsite[0].team;
|
||||
const { id: websiteId, userId } = row;
|
||||
const owner = teamUser[0];
|
||||
const canRemove = user.id === userId || user.id === owner.userId;
|
||||
return (
|
||||
<>
|
||||
<Link href={`/websites/${websiteId}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
{canRemove && (
|
||||
<TeamWebsiteRemoveButton teamId={teamId} websiteId={websiteId} onSave={onSave} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamWebsitesTable;
|
||||
9
src/app/(main)/settings/teams/[id]/page.js
Normal file
9
src/app/(main)/settings/teams/[id]/page.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import TeamSettings from './TeamSettings';
|
||||
|
||||
export default function ({ params }) {
|
||||
if (process.env.cloudMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TeamSettings teamId={params.id} />;
|
||||
}
|
||||
Reference in New Issue
Block a user