Renamed (app) folder to (main).

This commit is contained in:
Mike Cao
2023-10-03 16:05:17 -07:00
parent 5c15778c9b
commit c990459238
167 changed files with 48 additions and 114 deletions

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,9 @@
import TeamSettings from './TeamSettings';
export default function ({ params }) {
if (process.env.cloudMode) {
return null;
}
return <TeamSettings teamId={params.id} />;
}