├── components
├── index.js
├── Marketplace.js
├── Invitations.js
└── ShopTable.js
├── mutations
├── index.js
└── inviteShopOwner.js
├── screenshot.png
├── graphql
└── queries
│ └── shops.js
├── client
└── index.js
├── README.md
└── package.json
/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Marketplace } from "./Marketplace";
2 |
--------------------------------------------------------------------------------
/mutations/index.js:
--------------------------------------------------------------------------------
1 | export { default as inviteShopOwner } from "./inviteShopOwner.js";
2 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/outgrow/reaction-marketplace-ui/HEAD/screenshot.png
--------------------------------------------------------------------------------
/mutations/inviteShopOwner.js:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export default gql`
4 | mutation inviteShopOwner($input: InviteShopOwnerInput!) {
5 | inviteShopOwner(input: $input) {
6 | wasInviteSent
7 | }
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/graphql/queries/shops.js:
--------------------------------------------------------------------------------
1 | import gql from "graphql-tag";
2 |
3 | export default gql`
4 | query shops($first: ConnectionLimitInt, $last: ConnectionLimitInt, $offset: Int) {
5 | shops(first: $first, last: $last, offset: $offset) {
6 | pageInfo {
7 | endCursor
8 | startCursor
9 | hasNextPage
10 | hasPreviousPage
11 | }
12 | nodes {
13 | _id
14 | createdAt
15 | name
16 | owner {
17 | emailRecords {
18 | address
19 | }
20 | }
21 | productCount
22 | }
23 | }
24 | }
25 | `;
26 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withApollo } from "react-apollo";
3 | import Shopping from "mdi-material-ui/Shopping";
4 | import { registerOperatorRoute } from "/imports/client/ui";
5 | import { Marketplace } from "../components";
6 |
7 | registerOperatorRoute({
8 | group: "navigation",
9 | mainComponent: Marketplace,
10 | hocs: [
11 | withApollo
12 | ],
13 | path: "/marketplace",
14 | // eslint-disable-next-line react/display-name
15 | SidebarIconComponent: (props) => ,
16 | sidebarI18nLabel: "marketplaceSettings.sidebarLabel",
17 | shouldShowSidebarLink: (currentShop) => currentShop.shopType === "primary"
18 | });
19 |
--------------------------------------------------------------------------------
/components/Marketplace.js:
--------------------------------------------------------------------------------
1 | import React, { useState, Fragment } from "react";
2 | import Tab from "@material-ui/core/Tab";
3 | import Tabs from "@material-ui/core/Tabs";
4 | import PrimaryAppBar from "/imports/client/ui/components/PrimaryAppBar/PrimaryAppBar";
5 | import Invitations from "./Invitations";
6 | import ShopTable from "./ShopTable";
7 |
8 | export default function Marketplace() {
9 | const [currentTab, setCurrentTab] = useState(0);
10 |
11 | return (
12 |
13 |
14 |
15 | setCurrentTab(value)}>
16 |
17 |
18 |
19 |
20 | {currentTab === 0 &&
21 |
22 | }
23 |
24 | {currentTab === 1 &&
25 |
26 | }
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # reaction-marketplace-ui
2 |
3 | A user interface for [`@outgrow/reaction-marketplace`](https://github.com/outgrow/reaction-marketplace).
4 |
5 | 
6 |
7 | ## How to use
8 |
9 | Open a terminal in your `reaction-admin` project's `/imports/plugins/custom` directory.
10 |
11 | Clone the plugin:
12 |
13 | ```bash
14 | git clone https://github.com/outgrow/reaction-marketplace-ui
15 | ```
16 |
17 | Restart `reaction-admin`. You should now see the "Marketplace" link in your sidebar on [localhost:4080](https://localhost:4080).
18 |
19 | ## Help
20 |
21 | Need help integrating this plugin into your Reaction Commerce project? Simply looking for expert [Reaction Commerce developers](https://outgrow.io)? Want someone to train your team to use Reaction at its fullest?
22 |
23 | Whether it is just a one-hour consultation to get you set up or helping your team ship a whole project from start to finish, you can't go wrong by reaching out to us:
24 |
25 | * +1 (281) OUT-GROW
26 | * contact@outgrow.io
27 | * https://outgrow.io
28 |
--------------------------------------------------------------------------------
/components/Invitations.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useMutation } from "@apollo/react-hooks";
3 | import {Card, CardActions, CardContent, CardHeader, Grid, makeStyles} from "@material-ui/core";
4 | import Button from "@reactioncommerce/catalyst/Button";
5 | import Toast from "@reactioncommerce/catalyst/Toast";
6 | import { Components } from "@reactioncommerce/reaction-components";
7 | import { inviteShopOwner } from "../mutations";
8 | import InvitationTable from "/imports/plugins/core/accounts/client/components/Invitations.js";
9 |
10 | const useStyles = makeStyles(() => ({
11 | marginTop: {
12 | marginTop: "1rem"
13 | },
14 | rightAligned: {
15 | textAlign: "right"
16 | }
17 | }));
18 |
19 | export default function Invitations() {
20 | const classes = useStyles();
21 |
22 | const [isToastOpen, setIsToastOpen] = useState(false);
23 | const [shopOwnerInviteEmail, setShopOwnerInviteEmail] = useState("");
24 | const [shopOwnerName, setShopOwnerName] = useState("");
25 | const [toastMessage, setToastMessage] = useState("");
26 | const [toastVariant, setToastVariant] = useState("info");
27 |
28 | const [inviteShopOwnerMutation] = useMutation(inviteShopOwner, {
29 | onCompleted() {
30 | setIsToastOpen(true);
31 | setToastMessage(payload.wasInviteSent ? "Invite sent." : "Couldn't send invite.");
32 | setToastVariant(payload.wasInviteSent ? "success" : "error");
33 | },
34 | onError() {
35 | setIsToastOpen(true);
36 | setToastMessage(err.message);
37 | setToastVariant("error");
38 | }
39 | });
40 |
41 | const handleInviteShopOwner = async () => {
42 | await inviteShopOwnerMutation({
43 | variables: {
44 | input: {
45 | emailAddress: shopOwnerInviteEmail,
46 | name: shopOwnerName
47 | }
48 | }
49 | });
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 | setShopOwnerInviteEmail(event.target.value)}
63 | />
64 | setShopOwnerName(event.target.value)}
69 | />
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | setIsToastOpen(false)}
88 | message={toastMessage}
89 | variant={toastVariant}
90 | />
91 |
92 | );
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reaction-marketplace-ui",
3 | "version": "0.1.0",
4 | "description": "A user interface for @outgrowio/reaction-marketplace",
5 | "main": "client/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "out:grow",
10 | "license": "MIT",
11 | "dependencies": {
12 | "mdi-material-ui": "^6.11.0"
13 | },
14 | "babel": {
15 | "plugins": [
16 | [
17 | "@babel/plugin-proposal-class-properties",
18 | {
19 | "loose": false
20 | }
21 | ],
22 | [
23 | "lodash",
24 | {
25 | "id": [
26 | "lodash",
27 | "recompose"
28 | ]
29 | }
30 | ],
31 | [
32 | "module-resolver",
33 | {
34 | "root": [
35 | "./"
36 | ],
37 | "alias": {
38 | "@reactioncommerce/api-utils": "./imports/utils",
39 | "@reactioncommerce/reaction-collections": "./imports/plugins/core/collections",
40 | "@reactioncommerce/reaction-components": "./imports/plugins/core/components/lib",
41 | "@reactioncommerce/reaction-router": "./imports/plugins/core/router/lib",
42 | "@reactioncommerce/reaction-ui": "./imports/plugins/core/ui/client/components",
43 | "underscore": "lodash"
44 | }
45 | }
46 | ]
47 | ],
48 | "env": {
49 | "test": {
50 | "presets": [
51 | [
52 | "@babel/env",
53 | {
54 | "targets": {
55 | "node": "current"
56 | }
57 | }
58 | ],
59 | [
60 | "@babel/preset-react"
61 | ]
62 | ],
63 | "plugins": [
64 | "rewire-exports",
65 | "babel-plugin-inline-import",
66 | [
67 | "@babel/plugin-proposal-decorators",
68 | {
69 | "legacy": true
70 | }
71 | ],
72 | "@babel/plugin-proposal-function-sent",
73 | "@babel/plugin-proposal-export-namespace-from",
74 | "@babel/plugin-proposal-numeric-separator",
75 | "@babel/plugin-proposal-throw-expressions",
76 | "@babel/plugin-syntax-dynamic-import",
77 | "@babel/plugin-syntax-import-meta",
78 | [
79 | "@babel/plugin-proposal-class-properties",
80 | {
81 | "loose": false
82 | }
83 | ],
84 | "@babel/plugin-proposal-json-strings",
85 | [
86 | "module-resolver",
87 | {
88 | "root": [
89 | "./"
90 | ],
91 | "alias": {
92 | "@reactioncommerce/api-utils": "./imports/utils",
93 | "@reactioncommerce/reaction-collections": "./imports/plugins/core/collections",
94 | "@reactioncommerce/reaction-components": "./imports/plugins/core/components/lib",
95 | "@reactioncommerce/reaction-router": "./imports/plugins/core/router/lib",
96 | "@reactioncommerce/reaction-ui": "./imports/plugins/core/ui/client/components",
97 | "underscore": "lodash"
98 | }
99 | }
100 | ]
101 | ]
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/components/ShopTable.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useCallback, useMemo, useState } from "react";
2 | import i18next from "i18next";
3 | import { useSnackbar } from "notistack";
4 | import {Card, CardContent, CardHeader, Grid, makeStyles} from "@material-ui/core";
5 | import DataTable, { useDataTable } from "@reactioncommerce/catalyst/DataTable";
6 | import { useApolloClient } from "@apollo/react-hooks";
7 | import OrderDateCell from "/imports/plugins/core/orders/client/components/DataTable/OrderDateCell";
8 | import shopsQuery from "../graphql/queries/shops.js";
9 |
10 | const useStyles = makeStyles(() => ({
11 | marginTop: {
12 | marginTop: "1rem"
13 | }
14 | }));
15 |
16 | function ShopTable() {
17 | const classes = useStyles();
18 |
19 | const apolloClient = useApolloClient();
20 | const { enqueueSnackbar } = useSnackbar();
21 |
22 | const [isLoading, setIsLoading] = useState(false);
23 | const [pageCount, setPageCount] = useState(1);
24 | const [tableData, setTableData] = useState([]);
25 |
26 | const columns = useMemo(() => [
27 | {
28 | Header: i18next.t("marketplaceSettings.table.headers.id"),
29 | accessor: "_id",
30 | // eslint-disable-next-line react/no-multi-comp,react/display-name,react/prop-types
31 | Cell: ({ row }) => {row.values._id}
32 | },
33 | {
34 | Header: i18next.t("marketplaceSettings.table.headers.date"),
35 | accessor: "createdAt",
36 | // eslint-disable-next-line react/no-multi-comp,react/display-name,react/prop-types
37 | Cell: ({ row }) =>
38 | },
39 | {
40 | Header: i18next.t("marketplaceSettings.table.headers.name"),
41 | accessor: "name",
42 | // eslint-disable-next-line react/no-multi-comp,react/display-name,react/prop-types
43 | Cell: ({ row }) => {row.values.name}
44 | },
45 | {
46 | Header: i18next.t("marketplaceSettings.table.headers.owner"),
47 | accessor: "owner",
48 | // eslint-disable-next-line react/no-multi-comp,react/display-name,react/prop-types
49 | Cell: ({ row }) => {row.values.owner?.emailRecords[0]?.address}
50 | },
51 | {
52 | Header: i18next.t("marketplaceSettings.table.headers.productCount"),
53 | accessor: "productCount",
54 | // eslint-disable-next-line react/no-multi-comp,react/display-name,react/prop-types
55 | Cell: ({ row }) => {row.values.productCount}
56 | }
57 | ], []);
58 |
59 | const onFetchData = useCallback(async ({ pageIndex, pageSize }) => {
60 | // Wait for shop id to be available before fetching orders.
61 | setIsLoading(true);
62 |
63 | const { data, error } = await apolloClient.query({
64 | query: shopsQuery,
65 | variables: {
66 | first: pageSize,
67 | offset: pageIndex * pageSize
68 | },
69 | fetchPolicy: "network-only"
70 | });
71 |
72 | if (error && error.length) {
73 | enqueueSnackbar(i18next.t("admin.table.error", { variant: "error" }));
74 | return;
75 | }
76 |
77 | // Update the state with the fetched data as an array of objects and the calculated page count
78 | setTableData(data.shops.nodes);
79 | setPageCount(Math.ceil(data.shops.totalCount / pageSize));
80 |
81 | setIsLoading(false);
82 | }, [apolloClient, enqueueSnackbar]);
83 |
84 | const dataTableProps = useDataTable({
85 | columns,
86 | data: tableData,
87 | pageCount,
88 | onFetchData,
89 | getRowId: (row) => row.referenceId
90 | });
91 |
92 | return (
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | );
104 | }
105 |
106 | export default ShopTable;
107 |
--------------------------------------------------------------------------------