diff --git a/.eslintrc b/.eslintrc
index 2baf261..386b2b2 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -34,6 +34,7 @@
],
"no-param-reassign": ["error", { "props": true, "ignorePropertyModificationsFor": ["state"] }],
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
+ "react/require-default-props": "off",
"import/no-unresolved": ["off"],
"import/no-absolute-path": ["off"],
"import/order": ["off"],
diff --git a/Gemfile b/Gemfile
index 54656c0..9ed6f8d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -27,6 +27,7 @@ gem 'bcrypt', '~> 3.1.7'
gem 'bootsnap', '>= 1.4.2', require: false
gem 'active_model_serializers'
+gem 'js-routes'
gem 'kaminari'
gem 'ransack'
gem 'responders'
diff --git a/Gemfile.lock b/Gemfile.lock
index 4300873..4cfa6aa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -98,6 +98,8 @@ GEM
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
+ js-routes (2.2.4)
+ railties (>= 4)
json (2.6.2)
jsonapi-renderer (0.2.2)
kaminari (1.2.2)
@@ -278,6 +280,7 @@ DEPENDENCIES
capybara (>= 2.15)
factory_bot_rails
jbuilder (~> 2.7)
+ js-routes
kaminari
listen (~> 3.2)
pg (>= 0.18, < 2.0)
diff --git a/app/controllers/api/v1/tasks_controller.rb b/app/controllers/api/v1/tasks_controller.rb
index 0bac8d0..69137a8 100644
--- a/app/controllers/api/v1/tasks_controller.rb
+++ b/app/controllers/api/v1/tasks_controller.rb
@@ -16,6 +16,7 @@ def index
end
def create
+ params['task']['author_id'] = current_user.id
task = current_user.my_tasks.new(task_params)
task.save
diff --git a/app/javascript/forms/TaskForm.js b/app/javascript/forms/TaskForm.js
new file mode 100644
index 0000000..27ec634
--- /dev/null
+++ b/app/javascript/forms/TaskForm.js
@@ -0,0 +1,21 @@
+import { pick, propOr } from 'ramda';
+
+export default {
+ defaultAttributes(attributes) {
+ return {
+ name: '',
+ description: '',
+ ...attributes,
+ };
+ },
+
+ attributesToSubmit(task) {
+ const pertmittedKeys = ['id', 'name', 'description'];
+
+ return {
+ ...pick(pertmittedKeys, task),
+ assigneeId: propOr(null, 'id', task.assignee),
+ authorId: propOr(null, 'id', task.author),
+ };
+ },
+};
diff --git a/app/javascript/packs/components/AddPopup/AddPopup.js b/app/javascript/packs/components/AddPopup/AddPopup.js
new file mode 100644
index 0000000..8cb9c42
--- /dev/null
+++ b/app/javascript/packs/components/AddPopup/AddPopup.js
@@ -0,0 +1,86 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { has } from 'ramda';
+
+import Button from '@material-ui/core/Button';
+import Card from '@material-ui/core/Card';
+import CardActions from '@material-ui/core/CardActions';
+import CardContent from '@material-ui/core/CardContent';
+import CardHeader from '@material-ui/core/CardHeader';
+import CloseIcon from '@material-ui/icons/Close';
+import IconButton from '@material-ui/core/IconButton';
+import Modal from '@material-ui/core/Modal';
+import TextField from '@material-ui/core/TextField';
+
+import TaskForm from 'forms/TaskForm';
+
+import useStyles from './useStyles';
+
+function AddPopup({ onClose, onCardCreate }) {
+ const [task, changeTask] = useState(TaskForm.defaultAttributes());
+ const [isSaving, setSaving] = useState(false);
+ const [errors, setErrors] = useState({});
+ const handleCreate = () => {
+ setSaving(true);
+
+ onCardCreate(task).catch((error) => {
+ setSaving(false);
+ setErrors(error || {});
+
+ if (error instanceof Error) {
+ alert(`Creation Failed! Error: ${error.message}`);
+ }
+ });
+ };
+ const handleChangeTextField = (fieldName) => (event) => changeTask({ ...task, [fieldName]: event.target.value });
+ const styles = useStyles();
+
+ return (
+
+
+
+
+
+ }
+ title="Add New Task"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+AddPopup.propTypes = {
+ onClose: PropTypes.func.isRequired,
+ onCardCreate: PropTypes.func.isRequired,
+};
+
+export default AddPopup;
diff --git a/app/javascript/packs/components/AddPopup/index.js b/app/javascript/packs/components/AddPopup/index.js
new file mode 100644
index 0000000..d0f632d
--- /dev/null
+++ b/app/javascript/packs/components/AddPopup/index.js
@@ -0,0 +1,3 @@
+import AddPopup from './AddPopup';
+
+export default AddPopup;
diff --git a/app/javascript/packs/components/AddPopup/useStyles.js b/app/javascript/packs/components/AddPopup/useStyles.js
new file mode 100644
index 0000000..f2405b0
--- /dev/null
+++ b/app/javascript/packs/components/AddPopup/useStyles.js
@@ -0,0 +1,26 @@
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(() => ({
+ modal: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ outline: 0,
+ },
+
+ root: {
+ width: 465,
+ },
+
+ actions: {
+ display: 'flex',
+ justifyContent: 'right',
+ },
+
+ form: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/packs/components/ColumnHeader/ColumnHeader.js b/app/javascript/packs/components/ColumnHeader/ColumnHeader.js
new file mode 100644
index 0000000..7be18b5
--- /dev/null
+++ b/app/javascript/packs/components/ColumnHeader/ColumnHeader.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import IconButton from '@material-ui/core/IconButton';
+import SystemUpdateAltIcon from '@material-ui/icons/SystemUpdateAlt';
+
+import useStyles from './useStyles';
+
+function ColumnHeader({ column, onLoadMore }) {
+ const styles = useStyles();
+
+ const {
+ id,
+ title,
+ cards,
+ meta: { totalCount, currentPage, totalPages },
+ } = column;
+
+ const count = cards.length;
+
+ const handleLoadMore = () => onLoadMore(id, currentPage + 1);
+
+ return (
+
+
+ {title} ({count}/{totalCount || '…'})
+
+
+ {currentPage >= totalPages ? null : (
+ handleLoadMore()}>
+
+
+ )}
+
+
+ );
+}
+
+ColumnHeader.propTypes = {
+ column: PropTypes.shape({
+ id: PropTypes.string,
+ title: PropTypes.string,
+ cards: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
+ meta: PropTypes.shape({
+ totalCount: PropTypes.number,
+ currentPage: PropTypes.number,
+ totalPages: PropTypes.number,
+ }),
+ }),
+ onLoadMore: PropTypes.func,
+};
+
+export default ColumnHeader;
diff --git a/app/javascript/packs/components/ColumnHeader/index.js b/app/javascript/packs/components/ColumnHeader/index.js
new file mode 100644
index 0000000..14babfd
--- /dev/null
+++ b/app/javascript/packs/components/ColumnHeader/index.js
@@ -0,0 +1,3 @@
+import ColumnHeader from './ColumnHeader';
+
+export default ColumnHeader;
diff --git a/app/javascript/packs/components/ColumnHeader/useStyles.js b/app/javascript/packs/components/ColumnHeader/useStyles.js
new file mode 100644
index 0000000..a5eb578
--- /dev/null
+++ b/app/javascript/packs/components/ColumnHeader/useStyles.js
@@ -0,0 +1,12 @@
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(() => ({
+ root: {
+ height: 42,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/packs/components/EditPopup/EditPopup.js b/app/javascript/packs/components/EditPopup/EditPopup.js
new file mode 100644
index 0000000..66d4701
--- /dev/null
+++ b/app/javascript/packs/components/EditPopup/EditPopup.js
@@ -0,0 +1,100 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import { isNil } from 'ramda';
+
+import Button from '@material-ui/core/Button';
+import Card from '@material-ui/core/Card';
+import CardActions from '@material-ui/core/CardActions';
+import CardContent from '@material-ui/core/CardContent';
+import CircularProgress from '@material-ui/core/CircularProgress';
+import CardHeader from '@material-ui/core/CardHeader';
+import CloseIcon from '@material-ui/icons/Close';
+import IconButton from '@material-ui/core/IconButton';
+import Modal from '@material-ui/core/Modal';
+
+import Form from './components/Form';
+
+import useStyles from './useStyles';
+
+function EditPopup({ cardId, onClose, onCardDestroy, onCardLoad, onCardUpdate }) {
+ const [task, setTask] = useState(null);
+ const [isSaving, setSaving] = useState(false);
+ const [errors, setErrors] = useState({});
+ const styles = useStyles();
+
+ useEffect(() => {
+ onCardLoad(cardId).then(setTask);
+ }, []);
+
+ const handleCardUpdate = () => {
+ setSaving(true);
+
+ onCardUpdate(task).catch((error) => {
+ setSaving(false);
+ setErrors(error || {});
+
+ if (error instanceof Error) {
+ alert(`Update Failed! Error: ${error.message}`);
+ }
+ });
+ };
+
+ const handleCardDestroy = () => {
+ setSaving(true);
+
+ onCardDestroy(task).catch((error) => {
+ setSaving(false);
+
+ alert(`Destrucion Failed! Error: ${error.message}`);
+ });
+ };
+ const isLoading = isNil(task);
+
+ return (
+
+
+
+
+
+ }
+ title={isLoading ? 'Your task is loading. Please be patient.' : `Task # ${task.id} [${task.name}]`}
+ />
+
+ {isLoading ? (
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+EditPopup.propTypes = {
+ cardId: PropTypes.number.isRequired,
+ onClose: PropTypes.func.isRequired,
+ onCardDestroy: PropTypes.func.isRequired,
+ onCardLoad: PropTypes.func.isRequired,
+ onCardUpdate: PropTypes.func.isRequired,
+};
+
+export default EditPopup;
diff --git a/app/javascript/packs/components/EditPopup/components/Form.js b/app/javascript/packs/components/EditPopup/components/Form.js
new file mode 100644
index 0000000..47b121c
--- /dev/null
+++ b/app/javascript/packs/components/EditPopup/components/Form.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { has } from 'ramda';
+
+import TextField from '@material-ui/core/TextField';
+
+import useStyles from './useStyles';
+
+function Form({ errors, onChange, task }) {
+ const handleChangeTextField = (fieldName) => (event) => onChange({ ...task, [fieldName]: event.target.value });
+ const styles = useStyles();
+
+ return (
+
+ );
+}
+
+Form.propTypes = {
+ onChange: PropTypes.func.isRequired,
+ task: PropTypes.shape().isRequired,
+ errors: PropTypes.shape({
+ name: PropTypes.arrayOf(PropTypes.string),
+ description: PropTypes.arrayOf(PropTypes.string),
+ author: PropTypes.arrayOf(PropTypes.string),
+ assignee: PropTypes.arrayOf(PropTypes.string),
+ }),
+};
+
+Form.defaultProps = {
+ errors: {},
+};
+
+export default Form;
diff --git a/app/javascript/packs/components/EditPopup/components/useStyles.js b/app/javascript/packs/components/EditPopup/components/useStyles.js
new file mode 100644
index 0000000..8475a5d
--- /dev/null
+++ b/app/javascript/packs/components/EditPopup/components/useStyles.js
@@ -0,0 +1,10 @@
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles(() => ({
+ root: {
+ display: 'flex',
+ flexDirection: 'column',
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/packs/components/EditPopup/index.js b/app/javascript/packs/components/EditPopup/index.js
new file mode 100644
index 0000000..4d20f3d
--- /dev/null
+++ b/app/javascript/packs/components/EditPopup/index.js
@@ -0,0 +1,3 @@
+import EditPopup from './EditPopup';
+
+export default EditPopup;
diff --git a/app/javascript/packs/components/EditPopup/useStyles.js b/app/javascript/packs/components/EditPopup/useStyles.js
new file mode 100644
index 0000000..01fdc0b
--- /dev/null
+++ b/app/javascript/packs/components/EditPopup/useStyles.js
@@ -0,0 +1,26 @@
+import { makeStyles } from '@material-ui/core';
+
+const useStyles = makeStyles(() => ({
+ modal: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ outline: 0,
+ },
+
+ root: {
+ width: 465,
+ },
+
+ loader: {
+ display: 'flex',
+ justifyContent: 'center',
+ },
+
+ actions: {
+ display: 'flex',
+ justifyContent: 'flex-end',
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/packs/components/Task/Task.js b/app/javascript/packs/components/Task/Task.js
new file mode 100644
index 0000000..bfb3fe0
--- /dev/null
+++ b/app/javascript/packs/components/Task/Task.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Card from '@material-ui/core/Card';
+import CardHeader from '@material-ui/core/CardHeader';
+import CardContent from '@material-ui/core/CardContent';
+import Typography from '@material-ui/core/Typography';
+import IconButton from '@material-ui/core/IconButton';
+import EditIcon from '@material-ui/icons/Edit';
+
+import useStyles from './useStyles';
+
+function Task({ task, onClick }) {
+ const styles = useStyles;
+ const handleClick = () => onClick(task);
+ const action = (
+
+
+
+ );
+
+ return (
+
+
+
+
+ {task.description}
+
+
+
+ );
+}
+
+Task.propTypes = {
+ task: PropTypes.shape().isRequired,
+ onClick: PropTypes.func.isRequired,
+};
+
+export default Task;
diff --git a/app/javascript/packs/components/Task/index.js b/app/javascript/packs/components/Task/index.js
new file mode 100644
index 0000000..6dc333e
--- /dev/null
+++ b/app/javascript/packs/components/Task/index.js
@@ -0,0 +1,3 @@
+import Task from './Task';
+
+export default Task;
diff --git a/app/javascript/packs/components/Task/useStyles.js b/app/javascript/packs/components/Task/useStyles.js
new file mode 100644
index 0000000..44ab8b7
--- /dev/null
+++ b/app/javascript/packs/components/Task/useStyles.js
@@ -0,0 +1,9 @@
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(() => ({
+ root: {
+ width: 250,
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/packs/components/TaskBoard/TaskBoard.js b/app/javascript/packs/components/TaskBoard/TaskBoard.js
index 08ece16..e01e3ee 100644
--- a/app/javascript/packs/components/TaskBoard/TaskBoard.js
+++ b/app/javascript/packs/components/TaskBoard/TaskBoard.js
@@ -1,36 +1,177 @@
-import React from 'react';
-import Board from '@asseinfo/react-kanban';
-import '@asseinfo/react-kanban/dist/styles.css';
-
-const data = {
- columns: [
- {
- id: 1,
- title: 'Backlog',
- cards: [
- {
- id: 1,
- title: 'Add card',
- description: 'Add capability to add a card in a column',
- },
- ],
- },
- {
- id: 2,
- title: 'Doing',
- cards: [
- {
- id: 2,
- title: 'Drag-n-drop support',
- description: 'Move a card between the columns',
- },
- ],
- },
- ],
+import React, { useEffect, useState } from 'react';
+import KanbanBoard from '@asseinfo/react-kanban';
+import { propOr } from 'ramda';
+import Fab from '@material-ui/core/Fab';
+import AddIcon from '@material-ui/icons/Add';
+
+import Task from 'packs/components/Task';
+import AddPopup from 'packs/components/AddPopup';
+import EditPopup from 'packs/components/EditPopup';
+import ColumnHeader from 'packs/components/ColumnHeader';
+import useStyles from './useStyles';
+import TaskForm from 'forms/TaskForm';
+import TasksRepository from 'repositories/TasksRepository';
+
+const STATES = [
+ { key: 'new_task', value: 'New' },
+ { key: 'in_development', value: 'In Dev' },
+ { key: 'in_qa', value: 'In QA' },
+ { key: 'in_code_review', value: 'in CR' },
+ { key: 'ready_for_release', value: 'Ready for release' },
+ { key: 'released', value: 'Released' },
+ { key: 'archived', value: 'Archived' },
+];
+
+const MODES = {
+ ADD: 'add',
+ NONE: 'none',
+ EDIT: 'edit',
+};
+
+const initialBoard = {
+ columns: STATES.map((column) => ({
+ id: column.key,
+ title: column.value,
+ cards: [],
+ meta: {},
+ })),
};
function TaskBoard() {
- return ;
+ const [board, setBoard] = useState(initialBoard);
+ const [boardCards, setBoardCards] = useState([]);
+ const [mode, setMode] = useState(MODES.NONE);
+ const [openedTaskId, setOpenedTaskId] = useState(null);
+ useEffect(() => loadBoard(), []);
+ useEffect(() => generateBoard(), [boardCards]);
+
+ const loadColumn = (state, page, perPage) =>
+ TasksRepository.index({
+ q: { stateEq: state, s: 'id DESC' },
+ page,
+ perPage,
+ });
+
+ const loadColumnInitial = (state, page = 1, perPage = 10) => {
+ loadColumn(state, page, perPage).then(({ data }) => {
+ setBoardCards((prevState) => ({
+ ...prevState,
+ [state]: { cards: data.items, meta: data.meta },
+ }));
+ });
+ };
+
+ const loadColumnMore = (state, page = 1, perPage = 10) => {
+ loadColumn(state, page, perPage).then(({ data }) => {
+ setBoardCards((prevState) => ({
+ ...prevState,
+ [state]: { cards: [...prevState[state].cards, ...data.items], meta: data.meta },
+ }));
+ });
+ };
+
+ const generateBoard = () => {
+ const board = {
+ columns: STATES.map(({ key, value }) => ({
+ id: key,
+ title: value,
+ cards: propOr({}, 'cards', boardCards[key]),
+ meta: propOr({}, 'meta', boardCards[key]),
+ })),
+ };
+
+ setBoard(board);
+ };
+
+ const loadBoard = () => {
+ STATES.map(({ key }) => loadColumnInitial(key));
+ };
+
+ const handleCardDragEnd = (task, source, destination) => {
+ const transition = task.transitions.find(({ to }) => destination.toColumnId === to);
+ if (!transition) {
+ return null;
+ }
+
+ return TasksRepository.update(task.id, { stateEvent: transition.event })
+ .then(() => {
+ loadColumnInitial(destination.toColumnId);
+ loadColumnInitial(source.fromColumnId);
+ })
+ .catch((error) => {
+ alert(`Move failed! ${error.message}`);
+ });
+ };
+
+ const handleAddPopupOpen = () => {
+ setMode(MODES.ADD);
+ };
+
+ const handleClose = () => {
+ setMode(MODES.NONE);
+ };
+
+ const handleTaskCreate = (params) => {
+ const attributes = TaskForm.attributesToSubmit(params);
+ return TasksRepository.create(attributes).then(({ data: { task } }) => {
+ loadColumnInitial(task.state);
+ handleClose();
+ });
+ };
+
+ const styles = useStyles();
+
+ const loadTask = (id) => TasksRepository.show(id).then(({ data: { task } }) => task);
+
+ const handleTaskUpdate = (task) => {
+ const attributes = TaskForm.attributesToSubmit(task);
+
+ return TasksRepository.update(task.id, attributes).then(() => {
+ loadColumnInitial(task.state);
+ handleClose();
+ });
+ };
+
+ const handleTaskDestroy = (task) =>
+ TasksRepository.destroy(task.id).then(() => {
+ loadColumnInitial(task.state);
+ handleClose();
+ });
+
+ const handleEditPopupOpen = (task) => {
+ setOpenedTaskId(task.id);
+ setMode(MODES.EDIT);
+ };
+
+ const handleEditPopupClose = () => {
+ setMode(MODES.NONE);
+ setOpenedTaskId(null);
+ };
+
+ return (
+ <>
+ }
+ renderColumnHeader={(column) => }
+ onCardDragEnd={handleCardDragEnd}
+ >
+ {board}
+
+ {mode === MODES.ADD && }
+ {mode === MODES.EDIT && (
+
+ )}
+
+
+
+ >
+ );
}
export default TaskBoard;
diff --git a/app/javascript/packs/components/TaskBoard/useStyles.js b/app/javascript/packs/components/TaskBoard/useStyles.js
new file mode 100644
index 0000000..df3697f
--- /dev/null
+++ b/app/javascript/packs/components/TaskBoard/useStyles.js
@@ -0,0 +1,11 @@
+import { makeStyles } from '@material-ui/core/styles';
+
+const useStyles = makeStyles(() => ({
+ addButton: {
+ position: 'fixed',
+ bottom: 32,
+ right: 32,
+ },
+}));
+
+export default useStyles;
diff --git a/app/javascript/repositories/TasksRepository.js b/app/javascript/repositories/TasksRepository.js
new file mode 100644
index 0000000..f4d251a
--- /dev/null
+++ b/app/javascript/repositories/TasksRepository.js
@@ -0,0 +1,29 @@
+import routes from 'routes';
+import FetchHelper from 'utils/fetchHelper';
+
+export default {
+ index(params) {
+ const path = routes.apiV1TasksPath();
+ return FetchHelper.get(path, params);
+ },
+
+ show(id) {
+ const path = routes.apiV1TaskPath(id);
+ return FetchHelper.get(path);
+ },
+
+ update(id, task = {}) {
+ const path = routes.apiV1TaskPath(id);
+ return FetchHelper.put(path, { task });
+ },
+
+ create(task = {}) {
+ const path = routes.apiV1TasksPath();
+ return FetchHelper.post(path, { task });
+ },
+
+ destroy(id) {
+ const path = routes.apiV1TaskPath(id);
+ return FetchHelper.delete(path);
+ },
+};
diff --git a/app/javascript/routes/ApiRoutes.js b/app/javascript/routes/ApiRoutes.js
new file mode 100644
index 0000000..23abf7c
--- /dev/null
+++ b/app/javascript/routes/ApiRoutes.js
@@ -0,0 +1,776 @@
+/**
+ * File generated by js-routes 2.2.4
+ * Based on Rails 6.0.6 routes of App::Application
+ */
+const __jsr = (() => {
+ const hasProp = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
+ let NodeTypes;
+ (function (NodeTypes) {
+ NodeTypes[NodeTypes["GROUP"] = 1] = "GROUP";
+ NodeTypes[NodeTypes["CAT"] = 2] = "CAT";
+ NodeTypes[NodeTypes["SYMBOL"] = 3] = "SYMBOL";
+ NodeTypes[NodeTypes["OR"] = 4] = "OR";
+ NodeTypes[NodeTypes["STAR"] = 5] = "STAR";
+ NodeTypes[NodeTypes["LITERAL"] = 6] = "LITERAL";
+ NodeTypes[NodeTypes["SLASH"] = 7] = "SLASH";
+ NodeTypes[NodeTypes["DOT"] = 8] = "DOT";
+ })(NodeTypes || (NodeTypes = {}));
+ const isBrowser = typeof window !== "undefined";
+ const ModuleReferences = {
+ CJS: {
+ define(routes) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ module.exports = routes;
+ },
+ isSupported() {
+ return typeof module === "object";
+ },
+ },
+ AMD: {
+ define(routes) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ define([], function () {
+ return routes;
+ });
+ },
+ isSupported() {
+ return typeof define === "function" && !!define.amd;
+ },
+ },
+ UMD: {
+ define(routes) {
+ if (ModuleReferences.AMD.isSupported()) {
+ ModuleReferences.AMD.define(routes);
+ }
+ else {
+ if (ModuleReferences.CJS.isSupported()) {
+ try {
+ ModuleReferences.CJS.define(routes);
+ }
+ catch (error) {
+ if (error.name !== "TypeError")
+ throw error;
+ }
+ }
+ }
+ },
+ isSupported() {
+ return (ModuleReferences.AMD.isSupported() ||
+ ModuleReferences.CJS.isSupported());
+ },
+ },
+ ESM: {
+ define() {
+ // Module can only be defined using ruby code generation
+ },
+ isSupported() {
+ // Its impossible to check if "export" keyword is supported
+ return true;
+ },
+ },
+ NIL: {
+ define() {
+ // Defined using const __jsr =
+ },
+ isSupported() {
+ return true;
+ },
+ },
+ DTS: {
+ // Acts the same as ESM
+ define(routes) {
+ ModuleReferences.ESM.define(routes);
+ },
+ isSupported() {
+ return ModuleReferences.ESM.isSupported();
+ },
+ },
+ };
+ class ParametersMissing extends Error {
+ constructor(...keys) {
+ super(`Route missing required keys: ${keys.join(", ")}`);
+ this.keys = keys;
+ Object.setPrototypeOf(this, Object.getPrototypeOf(this));
+ this.name = ParametersMissing.name;
+ }
+ }
+ const UriEncoderSegmentRegex = /[^a-zA-Z0-9\-._~!$&'()*+,;=:@]/g;
+ const ReservedOptions = [
+ "anchor",
+ "trailing_slash",
+ "subdomain",
+ "host",
+ "port",
+ "protocol",
+ ];
+ class UtilsClass {
+ constructor() {
+ this.configuration = {
+ prefix: "",
+ default_url_options: {},
+ special_options_key: "_options",
+ serializer: null || this.default_serializer.bind(this),
+ };
+ }
+ default_serializer(value, prefix) {
+ if (this.is_nullable(value)) {
+ return "";
+ }
+ if (!prefix && !this.is_object(value)) {
+ throw new Error("Url parameters should be a javascript hash");
+ }
+ prefix = prefix || "";
+ const result = [];
+ if (this.is_array(value)) {
+ for (const element of value) {
+ result.push(this.default_serializer(element, prefix + "[]"));
+ }
+ }
+ else if (this.is_object(value)) {
+ for (let key in value) {
+ if (!hasProp(value, key))
+ continue;
+ let prop = value[key];
+ if (this.is_nullable(prop) && prefix) {
+ prop = "";
+ }
+ if (this.is_not_nullable(prop)) {
+ if (prefix) {
+ key = prefix + "[" + key + "]";
+ }
+ result.push(this.default_serializer(prop, key));
+ }
+ }
+ }
+ else {
+ if (this.is_not_nullable(value)) {
+ result.push(encodeURIComponent(prefix) + "=" + encodeURIComponent("" + value));
+ }
+ }
+ return result.join("&");
+ }
+ serialize(object) {
+ return this.configuration.serializer(object);
+ }
+ extract_options(number_of_params, args) {
+ const last_el = args[args.length - 1];
+ if ((args.length > number_of_params && last_el === 0) ||
+ (this.is_object(last_el) &&
+ !this.looks_like_serialized_model(last_el))) {
+ if (this.is_object(last_el)) {
+ delete last_el[this.configuration.special_options_key];
+ }
+ return {
+ args: args.slice(0, args.length - 1),
+ options: last_el,
+ };
+ }
+ else {
+ return { args, options: {} };
+ }
+ }
+ looks_like_serialized_model(object) {
+ return (this.is_object(object) &&
+ !(this.configuration.special_options_key in object) &&
+ ("id" in object || "to_param" in object || "toParam" in object));
+ }
+ path_identifier(object) {
+ const result = this.unwrap_path_identifier(object);
+ return this.is_nullable(result) || result === false ? "" : "" + result;
+ }
+ unwrap_path_identifier(object) {
+ let result = object;
+ if (!this.is_object(object)) {
+ return object;
+ }
+ if ("to_param" in object) {
+ result = object.to_param;
+ }
+ else if ("toParam" in object) {
+ result = object.toParam;
+ }
+ else if ("id" in object) {
+ result = object.id;
+ }
+ else {
+ result = object;
+ }
+ return this.is_callable(result) ? result.call(object) : result;
+ }
+ partition_parameters(parts, required_params, default_options, call_arguments) {
+ // eslint-disable-next-line prefer-const
+ let { args, options } = this.extract_options(parts.length, call_arguments);
+ if (args.length > parts.length) {
+ throw new Error("Too many parameters provided for path");
+ }
+ let use_all_parts = args.length > required_params.length;
+ const parts_options = {
+ ...this.configuration.default_url_options,
+ };
+ for (const key in options) {
+ const value = options[key];
+ if (!hasProp(options, key))
+ continue;
+ use_all_parts = true;
+ if (parts.includes(key)) {
+ parts_options[key] = value;
+ }
+ }
+ options = {
+ ...this.configuration.default_url_options,
+ ...default_options,
+ ...options,
+ };
+ const keyword_parameters = {};
+ let query_parameters = {};
+ for (const key in options) {
+ if (!hasProp(options, key))
+ continue;
+ const value = options[key];
+ if (key === "params") {
+ if (this.is_object(value)) {
+ query_parameters = {
+ ...query_parameters,
+ ...value,
+ };
+ }
+ else {
+ throw new Error("params value should always be an object");
+ }
+ }
+ else if (this.is_reserved_option(key)) {
+ keyword_parameters[key] = value;
+ }
+ else {
+ if (!this.is_nullable(value) &&
+ (value !== default_options[key] || required_params.includes(key))) {
+ query_parameters[key] = value;
+ }
+ }
+ }
+ const route_parts = use_all_parts ? parts : required_params;
+ let i = 0;
+ for (const part of route_parts) {
+ if (i < args.length) {
+ const value = args[i];
+ if (!hasProp(parts_options, part)) {
+ query_parameters[part] = value;
+ ++i;
+ }
+ }
+ }
+ return { keyword_parameters, query_parameters };
+ }
+ build_route(parts, required_params, default_options, route, absolute, args) {
+ const { keyword_parameters, query_parameters, } = this.partition_parameters(parts, required_params, default_options, args);
+ const missing_params = required_params.filter((param) => !hasProp(query_parameters, param) ||
+ this.is_nullable(query_parameters[param]));
+ if (missing_params.length) {
+ throw new ParametersMissing(...missing_params);
+ }
+ let result = this.get_prefix() + this.visit(route, query_parameters);
+ if (keyword_parameters.trailing_slash) {
+ result = result.replace(/(.*?)[/]?$/, "$1/");
+ }
+ const url_params = this.serialize(query_parameters);
+ if (url_params.length) {
+ result += "?" + url_params;
+ }
+ result += keyword_parameters.anchor
+ ? "#" + keyword_parameters.anchor
+ : "";
+ if (absolute) {
+ result = this.route_url(keyword_parameters) + result;
+ }
+ return result;
+ }
+ visit(route, parameters, optional = false) {
+ switch (route[0]) {
+ case NodeTypes.GROUP:
+ return this.visit(route[1], parameters, true);
+ case NodeTypes.CAT:
+ return this.visit_cat(route, parameters, optional);
+ case NodeTypes.SYMBOL:
+ return this.visit_symbol(route, parameters, optional);
+ case NodeTypes.STAR:
+ return this.visit_globbing(route[1], parameters, true);
+ case NodeTypes.LITERAL:
+ case NodeTypes.SLASH:
+ case NodeTypes.DOT:
+ return route[1];
+ default:
+ throw new Error("Unknown Rails node type");
+ }
+ }
+ is_not_nullable(object) {
+ return !this.is_nullable(object);
+ }
+ is_nullable(object) {
+ return object === undefined || object === null;
+ }
+ visit_cat(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ [_type, left, right], parameters, optional) {
+ const left_part = this.visit(left, parameters, optional);
+ let right_part = this.visit(right, parameters, optional);
+ if (optional &&
+ ((this.is_optional_node(left[0]) && !left_part) ||
+ (this.is_optional_node(right[0]) && !right_part))) {
+ return "";
+ }
+ // if left_part ends on '/' and right_part starts on '/'
+ if (left_part[left_part.length - 1] === "/" && right_part[0] === "/") {
+ // strip slash from right_part
+ // to prevent double slash
+ right_part = right_part.substring(1);
+ }
+ return left_part + right_part;
+ }
+ visit_symbol(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ [_type, key], parameters, optional) {
+ const value = this.path_identifier(parameters[key]);
+ delete parameters[key];
+ if (value.length) {
+ return this.encode_segment(value);
+ }
+ if (optional) {
+ return "";
+ }
+ else {
+ throw new ParametersMissing(key);
+ }
+ }
+ encode_segment(segment) {
+ return segment.replace(UriEncoderSegmentRegex, (str) => encodeURIComponent(str));
+ }
+ is_optional_node(node) {
+ return [NodeTypes.STAR, NodeTypes.SYMBOL, NodeTypes.CAT].includes(node);
+ }
+ build_path_spec(route, wildcard = false) {
+ let key;
+ switch (route[0]) {
+ case NodeTypes.GROUP:
+ return "(" + this.build_path_spec(route[1]) + ")";
+ case NodeTypes.CAT:
+ return (this.build_path_spec(route[1]) + this.build_path_spec(route[2]));
+ case NodeTypes.STAR:
+ return this.build_path_spec(route[1], true);
+ case NodeTypes.SYMBOL:
+ key = route[1];
+ if (wildcard) {
+ return (key.startsWith("*") ? "" : "*") + key;
+ }
+ else {
+ return ":" + key;
+ }
+ break;
+ case NodeTypes.SLASH:
+ case NodeTypes.DOT:
+ case NodeTypes.LITERAL:
+ return route[1];
+ default:
+ throw new Error("Unknown Rails node type");
+ }
+ }
+ visit_globbing(route, parameters, optional) {
+ const key = route[1];
+ let value = parameters[key];
+ delete parameters[key];
+ if (this.is_nullable(value)) {
+ return this.visit(route, parameters, optional);
+ }
+ if (this.is_array(value)) {
+ value = value.join("/");
+ }
+ const result = this.path_identifier(value);
+ return false
+ ? result
+ : encodeURI(result);
+ }
+ get_prefix() {
+ const prefix = this.configuration.prefix;
+ return prefix.match("/$")
+ ? prefix.substring(0, prefix.length - 1)
+ : prefix;
+ }
+ route(parts_table, route_spec, absolute = false) {
+ const required_params = [];
+ const parts = [];
+ const default_options = {};
+ for (const [part, { r: required, d: value }] of Object.entries(parts_table)) {
+ parts.push(part);
+ if (required) {
+ required_params.push(part);
+ }
+ if (this.is_not_nullable(value)) {
+ default_options[part] = value;
+ }
+ }
+ const result = (...args) => {
+ return this.build_route(parts, required_params, default_options, route_spec, absolute, args);
+ };
+ result.requiredParams = () => required_params;
+ result.toString = () => {
+ return this.build_path_spec(route_spec);
+ };
+ return result;
+ }
+ route_url(route_defaults) {
+ const hostname = route_defaults.host || this.current_host();
+ if (!hostname) {
+ return "";
+ }
+ const subdomain = route_defaults.subdomain
+ ? route_defaults.subdomain + "."
+ : "";
+ const protocol = route_defaults.protocol || this.current_protocol();
+ let port = route_defaults.port ||
+ (!route_defaults.host ? this.current_port() : undefined);
+ port = port ? ":" + port : "";
+ return protocol + "://" + subdomain + hostname + port;
+ }
+ current_host() {
+ var _a;
+ return (isBrowser && ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.hostname)) || "";
+ }
+ current_protocol() {
+ var _a, _b;
+ return ((isBrowser && ((_b = (_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.protocol) === null || _b === void 0 ? void 0 : _b.replace(/:$/, ""))) || "http");
+ }
+ current_port() {
+ var _a;
+ return (isBrowser && ((_a = window === null || window === void 0 ? void 0 : window.location) === null || _a === void 0 ? void 0 : _a.port)) || "";
+ }
+ is_object(value) {
+ return (typeof value === "object" &&
+ Object.prototype.toString.call(value) === "[object Object]");
+ }
+ is_array(object) {
+ return object instanceof Array;
+ }
+ is_callable(object) {
+ return typeof object === "function" && !!object.call;
+ }
+ is_reserved_option(key) {
+ return ReservedOptions.includes(key);
+ }
+ configure(new_config) {
+ this.configuration = { ...this.configuration, ...new_config };
+ return this.configuration;
+ }
+ config() {
+ return { ...this.configuration };
+ }
+ is_module_supported(name) {
+ return ModuleReferences[name].isSupported();
+ }
+ ensure_module_supported(name) {
+ if (!this.is_module_supported(name)) {
+ throw new Error(`${name} is not supported by runtime`);
+ }
+ }
+ define_module(name, module) {
+ this.ensure_module_supported(name);
+ ModuleReferences[name].define(module);
+ }
+ }
+ const Utils = new UtilsClass();
+ // We want this helper name to be short
+ const __jsr = {
+ r(parts_table, route_spec, absolute) {
+ return Utils.route(parts_table, route_spec, absolute);
+ },
+ };
+ const result = {
+ ...__jsr,
+ configure: (config) => {
+ return Utils.configure(config);
+ },
+ config: () => {
+ return Utils.config();
+ },
+ serialize: (object) => {
+ return Utils.serialize(object);
+ },
+ ...{},
+ };
+ Utils.define_module("ESM", result);
+ return result;
+})();
+export const configure = __jsr.configure;
+
+export const config = __jsr.config;
+
+export const serialize = __jsr.serialize;
+
+/**
+ * Generates rails route to
+ * /admin/users/:id(.:format)
+ * @param {any} id
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const adminUserPath = __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"admin"],[2,[7,"/"],[2,[6,"users"],[2,[7,"/"],[2,[3,"id"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /admin/users(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const adminUsersPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"admin"],[2,[7,"/"],[2,[6,"users"],[1,[2,[8,"."],[3,"format"]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /api/v1/tasks/:id(.:format)
+ * @param {any} id
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const apiV1TaskPath = __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"api"],[2,[7,"/"],[2,[6,"v1"],[2,[7,"/"],[2,[6,"tasks"],[2,[7,"/"],[2,[3,"id"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /api/v1/tasks(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const apiV1TasksPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"api"],[2,[7,"/"],[2,[6,"v1"],[2,[7,"/"],[2,[6,"tasks"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /board(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const boardPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"board"],[1,[2,[8,"."],[3,"format"]]]]]);
+
+/**
+ * Generates rails route to
+ * /developers(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const developersPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"developers"],[1,[2,[8,"."],[3,"format"]]]]]);
+
+/**
+ * Generates rails route to
+ * /admin/users/:id/edit(.:format)
+ * @param {any} id
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const editAdminUserPath = __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"admin"],[2,[7,"/"],[2,[6,"users"],[2,[7,"/"],[2,[3,"id"],[2,[7,"/"],[2,[6,"edit"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format)
+ * @param {any} id
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const editRailsConductorInboundEmailPath = __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"conductor"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"inbound_emails"],[2,[7,"/"],[2,[3,"id"],[2,[7,"/"],[2,[6,"edit"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /admin/users/new(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const newAdminUserPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"admin"],[2,[7,"/"],[2,[6,"users"],[2,[7,"/"],[2,[6,"new"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /developers/new(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const newDeveloperPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"developers"],[2,[7,"/"],[2,[6,"new"],[1,[2,[8,"."],[3,"format"]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/conductor/action_mailbox/inbound_emails/new(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const newRailsConductorInboundEmailPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"conductor"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"inbound_emails"],[2,[7,"/"],[2,[6,"new"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /session/new(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const newSessionPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"session"],[2,[7,"/"],[2,[6,"new"],[1,[2,[8,"."],[3,"format"]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format)
+ * @param {any} signedBlobId
+ * @param {any} variationKey
+ * @param {any} filename
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsBlobRepresentationPath = __jsr.r({"signed_blob_id":{"r":true},"variation_key":{"r":true},"filename":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"active_storage"],[2,[7,"/"],[2,[6,"representations"],[2,[7,"/"],[2,[3,"signed_blob_id"],[2,[7,"/"],[2,[3,"variation_key"],[2,[7,"/"],[2,[5,[3,"filename"]],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/conductor/action_mailbox/inbound_emails/:id(.:format)
+ * @param {any} id
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsConductorInboundEmailPath = __jsr.r({"id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"conductor"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"inbound_emails"],[2,[7,"/"],[2,[3,"id"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format)
+ * @param {any} inboundEmailId
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsConductorInboundEmailReroutePath = __jsr.r({"inbound_email_id":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"conductor"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[3,"inbound_email_id"],[2,[7,"/"],[2,[6,"reroute"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/conductor/action_mailbox/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsConductorInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"conductor"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/active_storage/direct_uploads(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsDirectUploadsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"active_storage"],[2,[7,"/"],[2,[6,"direct_uploads"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/active_storage/disk/:encoded_key/*filename(.:format)
+ * @param {any} encodedKey
+ * @param {any} filename
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsDiskServicePath = __jsr.r({"encoded_key":{"r":true},"filename":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"active_storage"],[2,[7,"/"],[2,[6,"disk"],[2,[7,"/"],[2,[3,"encoded_key"],[2,[7,"/"],[2,[5,[3,"filename"]],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/info(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsInfoPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"info"],[1,[2,[8,"."],[3,"format"]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/info/properties(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsInfoPropertiesPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"info"],[2,[7,"/"],[2,[6,"properties"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/info/routes(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsInfoRoutesPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"info"],[2,[7,"/"],[2,[6,"routes"],[1,[2,[8,"."],[3,"format"]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/mailers(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsMailersPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"mailers"],[1,[2,[8,"."],[3,"format"]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/mailgun/inbound_emails/mime(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsMailgunInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"mailgun"],[2,[7,"/"],[2,[6,"inbound_emails"],[2,[7,"/"],[2,[6,"mime"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/mandrill/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsMandrillInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"mandrill"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/mandrill/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsMandrillInboundHealthCheckPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"mandrill"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/postmark/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsPostmarkInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"postmark"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/relay/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsRelayInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"relay"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/action_mailbox/sendgrid/inbound_emails(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsSendgridInboundEmailsPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"action_mailbox"],[2,[7,"/"],[2,[6,"sendgrid"],[2,[7,"/"],[2,[6,"inbound_emails"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/active_storage/blobs/:signed_id/*filename(.:format)
+ * @param {any} signedId
+ * @param {any} filename
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const railsServiceBlobPath = __jsr.r({"signed_id":{"r":true},"filename":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"active_storage"],[2,[7,"/"],[2,[6,"blobs"],[2,[7,"/"],[2,[3,"signed_id"],[2,[7,"/"],[2,[5,[3,"filename"]],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]]]);
+
+/**
+ * Generates rails route to
+ * /
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const rootPath = __jsr.r({}, [7,"/"]);
+
+/**
+ * Generates rails route to
+ * /session(.:format)
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const sessionPath = __jsr.r({"format":{}}, [2,[7,"/"],[2,[6,"session"],[1,[2,[8,"."],[3,"format"]]]]]);
+
+/**
+ * Generates rails route to
+ * /rails/active_storage/disk/:encoded_token(.:format)
+ * @param {any} encodedToken
+ * @param {object | undefined} options
+ * @returns {string} route path
+ */
+export const updateRailsDiskServicePath = __jsr.r({"encoded_token":{"r":true},"format":{}}, [2,[7,"/"],[2,[6,"rails"],[2,[7,"/"],[2,[6,"active_storage"],[2,[7,"/"],[2,[6,"disk"],[2,[7,"/"],[2,[3,"encoded_token"],[1,[2,[8,"."],[3,"format"]]]]]]]]]]]);
+
diff --git a/app/javascript/routes/index.js b/app/javascript/routes/index.js
new file mode 100644
index 0000000..4d1fb88
--- /dev/null
+++ b/app/javascript/routes/index.js
@@ -0,0 +1,3 @@
+import * as routes from './ApiRoutes';
+
+export default routes;
diff --git a/app/javascript/utils/fetchHelper.js b/app/javascript/utils/fetchHelper.js
new file mode 100644
index 0000000..537f9b0
--- /dev/null
+++ b/app/javascript/utils/fetchHelper.js
@@ -0,0 +1,67 @@
+import axios from 'axios';
+import qs from 'qs';
+
+import { camelize, decamelize } from './keysConverter';
+
+function authenticityToken() {
+ const token = document.querySelector('meta[name="csrf-token"]');
+ return token ? token.content : null;
+}
+
+function headers() {
+ return {
+ Accept: '*/*',
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': authenticityToken(),
+ 'X-Requested-With': 'XMLHttpRequest',
+ };
+}
+
+axios.defaults.headers.post = headers();
+axios.defaults.headers.put = headers();
+axios.defaults.headers.delete = headers();
+axios.interceptors.response.use(null, (error) => {
+ if (error.response.status === 422) {
+ const {
+ response: { data: errors },
+ } = error;
+ return Promise.reject(camelize(errors.errors));
+ }
+
+ if (error.response.status === 500) {
+ return Promise.reject(new Error('Something went wrong, please retry again'));
+ }
+
+ return Promise.reject(error);
+});
+
+export default {
+ get(url, params = {}) {
+ return axios
+ .get(url, {
+ params: decamelize(params),
+ paramsSerializer: {
+ serialize: (parameters) => qs.stringify(parameters, { encode: false }),
+ },
+ })
+ .then(camelize);
+ },
+
+ post(url, json) {
+ const body = decamelize(json);
+
+ return axios.post(url, body).then(camelize);
+ },
+
+ put(url, json) {
+ const body = decamelize(json);
+
+ return axios.put(url, body).then(camelize);
+ },
+
+ delete(url, json) {
+ const body = decamelize(json);
+
+ return axios.delete(url, body).then(camelize);
+ },
+};
diff --git a/app/javascript/utils/keysConverter.js b/app/javascript/utils/keysConverter.js
new file mode 100644
index 0000000..b7238ee
--- /dev/null
+++ b/app/javascript/utils/keysConverter.js
@@ -0,0 +1,4 @@
+import humps from 'humps';
+
+export const decamelize = (obj) => humps.decamelizeKeys(obj);
+export const camelize = (obj) => humps.camelizeKeys(obj);
diff --git a/config/routes.rb b/config/routes.rb
index 8b28397..8425736 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,7 +12,7 @@
end
namespace :api do
- namespace :v1 do
+ namespace :v1, defaults: { format: 'json' } do
resources :tasks, only: %i[index show create update destroy]
end
end
diff --git a/lib/tasks/js_routes.rake b/lib/tasks/js_routes.rake
new file mode 100644
index 0000000..fa14992
--- /dev/null
+++ b/lib/tasks/js_routes.rake
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'js-routes'
+
+ROUTES_DIR = File.join('app', 'javascript', 'routes')
+namespace :js_routes do
+ desc 'Generate js routes for webpack'
+ task generate: :environment do
+ FileUtils.mkdir_p(Rails.root.join(ROUTES_DIR))
+ file_name = File.join('routes', 'ApiRoutes.js')
+ JsRoutes.generate!(file_name, camel_case: true)
+ end
+end
diff --git a/lib/tasks/tasks_seed.rake b/lib/tasks/tasks_seed.rake
new file mode 100644
index 0000000..a2c189f
--- /dev/null
+++ b/lib/tasks/tasks_seed.rake
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+namespace :tasks do
+ desc 'generates tasks'
+ task generate: :environment do
+ admin = Admin.find_or_create_by(first_name: 'admin', last_name: 'admin', email: 'admin@localhost.com')
+ 10.times do |i|
+ Task.create(
+ name: "card_name_#{i}",
+ description: "card_description_#{i}",
+ author: admin,
+ state: %w[new_task in_development in_qa in_codereview ready_for_release released archived].sample
+ )
+ end
+ end
+end
diff --git a/package.json b/package.json
index 4a830f9..35100d5 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,13 @@
"@asseinfo/react-kanban": "^2.2.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-react": "^7.18.6",
+ "@material-ui/core": "^4.12.4",
+ "@material-ui/icons": "^4.11.3",
"@rails/actioncable": "^6.0.0",
"@rails/activestorage": "^6.0.0",
"@rails/ujs": "^6.0.0",
"@rails/webpacker": "4.3.0",
+ "axios": "^1.2.1",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"eslint": "^8.29.0",
"eslint-config-airbnb": "^19.0.4",
@@ -22,9 +25,12 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
+ "humps": "^2.0.1",
"material-design-lite": "^1.3.0",
"prettier": "^2.8.0",
"prop-types": "^15.8.1",
+ "qs": "^6.11.0",
+ "ramda": "^0.28.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^5.3.6",
diff --git a/test/controllers/api/v1/tasks_controller_test.rb b/test/controllers/api/v1/tasks_controller_test.rb
index 2197fa0..d4ce848 100644
--- a/test/controllers/api/v1/tasks_controller_test.rb
+++ b/test/controllers/api/v1/tasks_controller_test.rb
@@ -49,5 +49,4 @@ class Api::V1::TasksControllerTest < ActionController::TestCase
assert !Task.where(id: task.id).exists?
end
-
end
diff --git a/yarn.lock b/yarn.lock
index 2a1d6b8..e447385 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -947,7 +947,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.20.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3"
integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==
@@ -993,6 +993,11 @@
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
+"@emotion/hash@^0.8.0":
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
+ integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+
"@emotion/is-prop-valid@^1.1.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz#7f2d35c97891669f7e276eb71c83376a5dc44c83"
@@ -1099,6 +1104,77 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
+"@material-ui/core@^4.12.4":
+ version "4.12.4"
+ resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.4.tgz#4ac17488e8fcaf55eb6a7f5efb2a131e10138a73"
+ integrity sha512-tr7xekNlM9LjA6pagJmL8QCgZXaubWUwkJnoYcMKd4gw/t4XiyvnTkjdGrUVicyB2BsdaAv1tvow45bPM4sSwQ==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+ "@material-ui/styles" "^4.11.5"
+ "@material-ui/system" "^4.12.2"
+ "@material-ui/types" "5.1.0"
+ "@material-ui/utils" "^4.11.3"
+ "@types/react-transition-group" "^4.2.0"
+ clsx "^1.0.4"
+ hoist-non-react-statics "^3.3.2"
+ popper.js "1.16.1-lts"
+ prop-types "^15.7.2"
+ react-is "^16.8.0 || ^17.0.0"
+ react-transition-group "^4.4.0"
+
+"@material-ui/icons@^4.11.3":
+ version "4.11.3"
+ resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.11.3.tgz#b0693709f9b161ce9ccde276a770d968484ecff1"
+ integrity sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+
+"@material-ui/styles@^4.11.5":
+ version "4.11.5"
+ resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.5.tgz#19f84457df3aafd956ac863dbe156b1d88e2bbfb"
+ integrity sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+ "@emotion/hash" "^0.8.0"
+ "@material-ui/types" "5.1.0"
+ "@material-ui/utils" "^4.11.3"
+ clsx "^1.0.4"
+ csstype "^2.5.2"
+ hoist-non-react-statics "^3.3.2"
+ jss "^10.5.1"
+ jss-plugin-camel-case "^10.5.1"
+ jss-plugin-default-unit "^10.5.1"
+ jss-plugin-global "^10.5.1"
+ jss-plugin-nested "^10.5.1"
+ jss-plugin-props-sort "^10.5.1"
+ jss-plugin-rule-value-function "^10.5.1"
+ jss-plugin-vendor-prefixer "^10.5.1"
+ prop-types "^15.7.2"
+
+"@material-ui/system@^4.12.2":
+ version "4.12.2"
+ resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.12.2.tgz#f5c389adf3fce4146edd489bf4082d461d86aa8b"
+ integrity sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+ "@material-ui/utils" "^4.11.3"
+ csstype "^2.5.2"
+ prop-types "^15.7.2"
+
+"@material-ui/types@5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
+ integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
+
+"@material-ui/utils@^4.11.3":
+ version "4.11.3"
+ resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.3.tgz#232bd86c4ea81dab714f21edad70b7fdf0253942"
+ integrity sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==
+ dependencies:
+ "@babel/runtime" "^7.4.4"
+ prop-types "^15.7.2"
+ react-is "^16.8.0 || ^17.0.0"
+
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -1323,6 +1399,13 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
+"@types/react-transition-group@^4.2.0":
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
+ integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react@*":
version "18.0.26"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
@@ -1880,6 +1963,15 @@ axe-core@^4.4.3:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.5.2.tgz#823fdf491ff717ac3c58a52631d4206930c1d9f7"
integrity sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==
+axios@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a"
+ integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==
+ dependencies:
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -2491,6 +2583,11 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
+clsx@^1.0.4:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
+ integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
+
coa@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
@@ -2558,7 +2655,7 @@ colorette@^2.0.10:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
-combined-stream@^1.0.6, combined-stream@~1.0.6:
+combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@@ -2925,6 +3022,14 @@ css-tree@^1.1.2:
mdn-data "2.0.14"
source-map "^0.6.1"
+css-vendor@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
+ integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
+ dependencies:
+ "@babel/runtime" "^7.8.3"
+ is-in-browser "^1.0.2"
+
css-what@^3.2.1:
version "3.4.2"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
@@ -3020,6 +3125,11 @@ csso@^4.0.2:
dependencies:
css-tree "^1.1.2"
+csstype@^2.5.2:
+ version "2.6.21"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e"
+ integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==
+
csstype@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
@@ -3205,6 +3315,14 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dom-helpers@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
+ integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
+ dependencies:
+ "@babel/runtime" "^7.8.7"
+ csstype "^3.0.2"
+
dom-serializer@0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
@@ -4013,7 +4131,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
-follow-redirects@^1.0.0:
+follow-redirects@^1.0.0, follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
@@ -4028,6 +4146,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
+form-data@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+ integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -4569,6 +4696,16 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+humps@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa"
+ integrity sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==
+
+hyphenate-style-name@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
+ integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+
iconv-lite@0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -4909,6 +5046,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-in-browser@^1.0.2, is-in-browser@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
+ integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==
+
is-negative-zero@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
@@ -5170,6 +5312,76 @@ jsprim@^1.2.2:
json-schema "0.4.0"
verror "1.10.0"
+jss-plugin-camel-case@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.2.tgz#76dddfa32f9e62d17daa4e3504991fd0933b89e1"
+ integrity sha512-wgBPlL3WS0WDJ1lPJcgjux/SHnDuu7opmgQKSraKs4z8dCCyYMx9IDPFKBXQ8Q5dVYij1FFV0WdxyhuOOAXuTg==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ hyphenate-style-name "^1.0.3"
+ jss "10.9.2"
+
+jss-plugin-default-unit@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.2.tgz#3e7f4a1506b18d8fe231554fd982439feb2a9c53"
+ integrity sha512-pYg0QX3bBEFtTnmeSI3l7ad1vtHU42YEEpgW7pmIh+9pkWNWb5dwS/4onSfAaI0kq+dOZHzz4dWe+8vWnanoSg==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.2"
+
+jss-plugin-global@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.9.2.tgz#e7f2ad4a5e8e674fb703b04b57a570b8c3e5c2c2"
+ integrity sha512-GcX0aE8Ef6AtlasVrafg1DItlL/tWHoC4cGir4r3gegbWwF5ZOBYhx04gurPvWHC8F873aEGqge7C17xpwmp2g==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.2"
+
+jss-plugin-nested@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.9.2.tgz#3aa2502816089ecf3981e1a07c49b276d67dca63"
+ integrity sha512-VgiOWIC6bvgDaAL97XCxGD0BxOKM0K0zeB/ECyNaVF6FqvdGB9KBBWRdy2STYAss4VVA7i5TbxFZN+WSX1kfQA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.2"
+ tiny-warning "^1.0.2"
+
+jss-plugin-props-sort@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.2.tgz#645f6c8f179309667b3e6212f66b59a32fb3f01f"
+ integrity sha512-AP1AyUTbi2szylgr+O0OB7gkIxEGzySLITZ2GpsaoX72YMCGI2jYAc+WUhPfvUnZYiauF4zTnN4V4TGuvFjJlw==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.2"
+
+jss-plugin-rule-value-function@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.2.tgz#9afe07596e477123cbf11120776be6a64494541f"
+ integrity sha512-vf5ms8zvLFMub6swbNxvzsurHfUZ5Shy5aJB2gIpY6WNA3uLinEcxYyraQXItRHi5ivXGqYciFDRM2ZoVoRZ4Q==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.2"
+ tiny-warning "^1.0.2"
+
+jss-plugin-vendor-prefixer@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.2.tgz#410a0f3b9f8dbbfba58f4d329134df4849aa1237"
+ integrity sha512-SxcEoH+Rttf9fEv6KkiPzLdXRmI6waOTcMkbbEFgdZLDYNIP9UKNHFy6thhbRKqv0XMQZdrEsbDyV464zE/dUA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ css-vendor "^2.0.8"
+ jss "10.9.2"
+
+jss@10.9.2, jss@^10.5.1:
+ version "10.9.2"
+ resolved "https://registry.yarnpkg.com/jss/-/jss-10.9.2.tgz#9379be1f195ef98011dfd31f9448251bd61b95a9"
+ integrity sha512-b8G6rWpYLR4teTUbGd4I4EsnWjg7MN0Q5bSsjKhVkJVjhQDy2KzkbD2AW3TuT0RYZVmZZHKIrXDn6kjU14qkUg==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ csstype "^3.0.2"
+ is-in-browser "^1.1.3"
+ tiny-warning "^1.0.2"
+
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea"
@@ -6378,6 +6590,11 @@ pnp-webpack-plugin@^1.5.0:
dependencies:
ts-pnp "^1.1.6"
+popper.js@1.16.1-lts:
+ version "1.16.1-lts"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
+ integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==
+
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
@@ -7060,7 +7277,7 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==
-prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7077,6 +7294,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -7149,7 +7371,7 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
-qs@6.11.0:
+qs@6.11.0, qs@^6.11.0:
version "6.11.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
@@ -7189,6 +7411,11 @@ raf-schd@^4.0.2:
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+ramda@^0.28.0:
+ version "0.28.0"
+ resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97"
+ integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==
+
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -7245,7 +7472,7 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-is@^17.0.2:
+"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -7262,6 +7489,16 @@ react-redux@^7.2.0:
prop-types "^15.7.2"
react-is "^17.0.2"
+react-transition-group@^4.4.0:
+ version "4.4.5"
+ resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
+ integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@@ -8439,6 +8676,11 @@ tiny-invariant@^1.0.6:
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
+tiny-warning@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
+ integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
+
to-arraybuffer@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"