Moved code into src folder. Added build for component library.

This commit is contained in:
Mike Cao
2023-08-21 02:06:09 -07:00
parent 7a7233ead4
commit ede658771e
490 changed files with 749 additions and 442 deletions

View File

@@ -0,0 +1,27 @@
import { Button, Icon, Text, Modal, Icons, ModalTrigger } from 'react-basics';
import UserAddForm from './UserAddForm';
import useMessages from 'components/hooks/useMessages';
export function UserAddButton({ onSave }) {
const { formatMessage, labels } = useMessages();
const handleSave = () => {
onSave();
};
return (
<ModalTrigger>
<Button variant="primary">
<Icon>
<Icons.Plus />
</Icon>
<Text>{formatMessage(labels.createUser)}</Text>
</Button>
<Modal title={formatMessage(labels.createUser)}>
{close => <UserAddForm onSave={handleSave} onClose={close} />}
</Modal>
</ModalTrigger>
);
}
export default UserAddButton;

View File

@@ -0,0 +1,76 @@
import {
Dropdown,
Item,
Form,
FormRow,
FormButtons,
FormInput,
TextField,
PasswordField,
SubmitButton,
Button,
} from 'react-basics';
import useApi from 'components/hooks/useApi';
import { ROLES } from 'lib/constants';
import useMessages from 'components/hooks/useMessages';
export function UserAddForm({ onSave, onClose }) {
const { post, useMutation } = useApi();
const { mutate, error, isLoading } = useMutation(data => post(`/users`, data));
const { formatMessage, labels } = useMessages();
const handleSubmit = async data => {
mutate(data, {
onSuccess: async () => {
onSave(data);
onClose();
},
});
};
const renderValue = value => {
if (value === ROLES.user) {
return formatMessage(labels.user);
}
if (value === ROLES.admin) {
return formatMessage(labels.admin);
}
if (value === ROLES.viewOnly) {
return formatMessage(labels.viewOnly);
}
};
return (
<Form onSubmit={handleSubmit} error={error}>
<FormRow label={formatMessage(labels.username)}>
<FormInput name="username" rules={{ required: formatMessage(labels.required) }}>
<TextField autoComplete="new-username" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.password)}>
<FormInput name="password" rules={{ required: formatMessage(labels.required) }}>
<PasswordField autoComplete="new-password" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.role)}>
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
<Dropdown renderValue={renderValue}>
<Item key={ROLES.viewOnly}>{formatMessage(labels.viewOnly)}</Item>
<Item key={ROLES.user}>{formatMessage(labels.user)}</Item>
<Item key={ROLES.admin}>{formatMessage(labels.admin)}</Item>
</Dropdown>
</FormInput>
</FormRow>
<FormButtons flex>
<SubmitButton variant="primary" disabled={false}>
{formatMessage(labels.save)}
</SubmitButton>
<Button disabled={isLoading} onClick={onClose}>
{formatMessage(labels.cancel)}
</Button>
</FormButtons>
</Form>
);
}
export default UserAddForm;

View File

@@ -0,0 +1,37 @@
import { useMutation } from '@tanstack/react-query';
import { Button, Form, FormButtons, SubmitButton } from 'react-basics';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
export function UserDeleteForm({ userId, username, onSave, onClose }) {
const { formatMessage, FormattedMessage, labels, messages } = useMessages();
const { del } = useApi();
const { mutate, error, isLoading } = useMutation(() => del(`/users/${userId}`));
const handleSubmit = async data => {
mutate(data, {
onSuccess: async () => {
onSave();
onClose();
},
});
};
return (
<Form onSubmit={handleSubmit} error={error}>
<p>
<FormattedMessage {...messages.confirmDelete} values={{ target: <b>{username}</b> }} />
</p>
<FormButtons flex>
<SubmitButton variant="danger" disabled={isLoading}>
{formatMessage(labels.delete)}
</SubmitButton>
<Button disabled={isLoading} onClick={onClose}>
{formatMessage(labels.cancel)}
</Button>
</FormButtons>
</Form>
);
}
export default UserDeleteForm;

View File

@@ -0,0 +1,76 @@
import {
Dropdown,
Item,
Form,
FormRow,
FormButtons,
FormInput,
TextField,
SubmitButton,
PasswordField,
} from 'react-basics';
import useApi from 'components/hooks/useApi';
import { ROLES } from 'lib/constants';
import useMessages from 'components/hooks/useMessages';
export function UserEditForm({ userId, data, onSave }) {
const { formatMessage, labels, messages } = useMessages();
const { post, useMutation } = useApi();
const { mutate, error } = useMutation(({ username, password, role }) =>
post(`/users/${userId}`, { username, password, role }),
);
const handleSubmit = async data => {
mutate(data, {
onSuccess: async () => {
onSave(data);
},
});
};
const renderValue = value => {
if (value === ROLES.user) {
return formatMessage(labels.user);
}
if (value === ROLES.admin) {
return formatMessage(labels.admin);
}
if (value === ROLES.viewOnly) {
return formatMessage(labels.viewOnly);
}
};
return (
<Form onSubmit={handleSubmit} error={error} values={data} style={{ width: 300 }}>
<FormRow label={formatMessage(labels.username)}>
<FormInput name="username">
<TextField />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.password)}>
<FormInput
name="password"
rules={{
minLength: { value: 8, message: formatMessage(messages.minPasswordLength, { n: 8 }) },
}}
>
<PasswordField autoComplete="new-password" />
</FormInput>
</FormRow>
<FormRow label={formatMessage(labels.role)}>
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
<Dropdown renderValue={renderValue}>
<Item key={ROLES.viewOnly}>{formatMessage(labels.viewOnly)}</Item>
<Item key={ROLES.user}>{formatMessage(labels.user)}</Item>
<Item key={ROLES.admin}>{formatMessage(labels.admin)}</Item>
</Dropdown>
</FormInput>
</FormRow>
<FormButtons>
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
</FormButtons>
</Form>
);
}
export default UserEditForm;

View File

@@ -0,0 +1,67 @@
import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics';
import Link from 'next/link';
import UserEditForm from 'components/pages/settings/users/UserEditForm';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import useApi from 'components/hooks/useApi';
import UserWebsites from './UserWebsites';
import useMessages from 'components/hooks/useMessages';
export function UserSettings({ userId }) {
const { formatMessage, labels, messages } = useMessages();
const [edit, setEdit] = useState(false);
const [values, setValues] = useState(null);
const [tab, setTab] = useState('details');
const { get, useQuery } = useApi();
const { showToast } = useToasts();
const { data, isLoading } = useQuery(
['user', userId],
() => {
if (userId) {
return get(`/users/${userId}`);
}
},
{ cacheTime: 0 },
);
const handleSave = data => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
if (data) {
setValues(state => ({ ...state, ...data }));
}
if (edit) {
setEdit(false);
}
};
useEffect(() => {
if (data) {
setValues(data);
}
}, [data]);
return (
<Page loading={isLoading || !values}>
<PageHeader
title={
<Breadcrumbs>
<Item>
<Link href="/settings/users">{formatMessage(labels.users)}</Link>
</Item>
<Item>{values?.username}</Item>
</Breadcrumbs>
}
/>
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
<Item key="details">{formatMessage(labels.details)}</Item>
<Item key="websites">{formatMessage(labels.websites)}</Item>
</Tabs>
{tab === 'details' && <UserEditForm userId={userId} data={values} onSave={handleSave} />}
{tab === 'websites' && <UserWebsites userId={userId} />}
</Page>
);
}
export default UserSettings;

View File

@@ -0,0 +1,36 @@
import Page from 'components/layout/Page';
import useApi from 'components/hooks/useApi';
import WebsitesTable from 'components/pages/settings/websites/WebsitesTable';
import useApiFilter from 'components/hooks/useApiFilter';
export function UserWebsites({ userId }) {
const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } =
useApiFilter();
const { get, useQuery } = useApi();
const { data, isLoading, error } = useQuery(
['user:websites', userId, filter, page, pageSize],
() =>
get(`/users/${userId}/websites`, {
filter,
page,
pageSize,
}),
);
const hasData = data && data.length !== 0;
return (
<Page loading={isLoading} error={error}>
{hasData && (
<WebsitesTable
data={data}
onFilterChange={handleFilterChange}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
filterValue={filter}
/>
)}
</Page>
);
}
export default UserWebsites;

View File

@@ -0,0 +1,68 @@
import { useToasts } from 'react-basics';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import UsersTable from './UsersTable';
import UserAddButton from './UserAddButton';
import useApi from 'components/hooks/useApi';
import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages';
import useApiFilter from 'components/hooks/useApiFilter';
export function UsersList() {
const { formatMessage, labels, messages } = useMessages();
const { user } = useUser();
const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } =
useApiFilter();
const { get, useQuery } = useApi();
const { data, isLoading, error, refetch } = useQuery(
['user', filter, page, pageSize],
() =>
get(`/users`, {
filter,
page,
pageSize,
}),
{
enabled: !!user,
},
);
const { showToast } = useToasts();
const hasData = data && data.length !== 0;
const handleSave = () => {
refetch().then(() => showToast({ message: formatMessage(messages.saved), variant: 'success' }));
};
const handleDelete = () => {
refetch().then(() =>
showToast({ message: formatMessage(messages.userDeleted), variant: 'success' }),
);
};
return (
<Page loading={isLoading} error={error}>
<PageHeader title={formatMessage(labels.users)}>
<UserAddButton onSave={handleSave} />
</PageHeader>
{(hasData || filter) && (
<UsersTable
data={data}
onDelete={handleDelete}
onFilterChange={handleFilterChange}
onPageChange={handlePageChange}
onPageSizeChange={handlePageSizeChange}
filterValue={filter}
/>
)}
{!hasData && !filter && (
<EmptyPlaceholder message={formatMessage(messages.noUsers)}>
<UserAddButton onSave={handleSave} />
</EmptyPlaceholder>
)}
</Page>
);
}
export default UsersList;

View File

@@ -0,0 +1,93 @@
import { Button, Text, Icon, Icons, ModalTrigger, Modal } from 'react-basics';
import { formatDistance } from 'date-fns';
import Link from 'next/link';
import useUser from 'components/hooks/useUser';
import UserDeleteForm from './UserDeleteForm';
import { ROLES } from 'lib/constants';
import useMessages from 'components/hooks/useMessages';
import SettingsTable from 'components/common/SettingsTable';
import useLocale from 'components/hooks/useLocale';
export function UsersTable({
data = { data: [] },
onDelete,
filterValue,
onFilterChange,
onPageChange,
onPageSizeChange,
}) {
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const { dateLocale } = useLocale();
const columns = [
{ name: 'username', label: formatMessage(labels.username) },
{ name: 'role', label: formatMessage(labels.role) },
{ name: 'created', label: formatMessage(labels.created) },
{ name: 'action', label: ' ' },
];
const cellRender = (row, data, key) => {
if (key === 'created') {
return formatDistance(new Date(row.createdAt), new Date(), {
addSuffix: true,
locale: dateLocale,
});
}
if (key === 'role') {
return formatMessage(
labels[Object.keys(ROLES).find(key => ROLES[key] === row.role)] || labels.unknown,
);
}
return data[key];
};
return (
<SettingsTable
data={data}
columns={columns}
cellRender={cellRender}
showSearch={true}
showPaging={true}
onFilterChange={onFilterChange}
onPageChange={onPageChange}
onPageSizeChange={onPageSizeChange}
filterValue={filterValue}
>
{row => {
return (
<>
<Link href={`/settings/users/${row.id}`}>
<Button>
<Icon>
<Icons.Edit />
</Icon>
<Text>{formatMessage(labels.edit)}</Text>
</Button>
</Link>
<ModalTrigger disabled={row.id === user.id}>
<Button disabled={row.id === user.id}>
<Icon>
<Icons.Trash />
</Icon>
<Text>{formatMessage(labels.delete)}</Text>
</Button>
<Modal title={formatMessage(labels.deleteUser)}>
{close => (
<UserDeleteForm
userId={row.id}
username={row.username}
onSave={onDelete}
onClose={close}
/>
)}
</Modal>
</ModalTrigger>
</>
);
}}
</SettingsTable>
);
}
export default UsersTable;