├── .gitignore ├── .gitpod.yml ├── .prettierignore ├── LICENSE ├── components ├── Callout.js ├── ChannelCards.js ├── ChannelDiagram.js ├── Connector.js ├── Graypaper.js ├── IntroDiagram.js ├── KeyPopper.js ├── LinkDiagram.js ├── MatchDiagram.js ├── OrderCard.js ├── Patterns.js ├── PlatformAdder.js ├── PlatformCard.js ├── Sandpack.js ├── ShopCards.js ├── ShopDiagram.js ├── Steps.js └── Video.js ├── next.config.js ├── package.json ├── pages ├── _app.js ├── _document.js ├── api-reference.mdx ├── channels.mdx ├── deployment.mdx ├── how-to-guides │ ├── create-custom-channel.mdx │ ├── create-custom-shop.mdx │ ├── integrate-channel-platform.mdx │ ├── integrate-shop-platform.mdx │ └── meta.json ├── index.mdx ├── meta.json └── shops.mdx ├── public ├── favicon │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── favicon.svg │ ├── safari-pinned-tab.svg │ └── site.webmanifest └── images │ ├── aliexpress.svg │ ├── artificial-intelligence.jpg │ ├── avatar.jpg │ ├── bigcommerce.svg │ ├── box.svg │ ├── box2.svg │ ├── cart.png │ ├── channels.png │ ├── channels.svg │ ├── conti.jpeg │ ├── conti2.png │ ├── dashboard.svg │ ├── deliverr.png │ ├── deliverr.svg │ ├── doordash.svg │ ├── easypost.svg │ ├── fulfillment.jpeg │ ├── header-img-mobile.svg │ ├── logoicon.svg │ ├── magento.svg │ ├── openship.svg │ ├── printful.svg │ ├── search.png │ ├── shipbob.svg │ ├── shipbobIcon.svg │ ├── shiphero.svg │ ├── shipheroFull.svg │ ├── shipping.jpg │ ├── shippo.svg │ ├── shipstation.svg │ ├── shipwire.svg │ ├── shopify.svg │ ├── shopifyIcon.svg │ ├── shops.png │ ├── shops.svg │ ├── stripe.svg │ ├── tracking.png │ ├── volusion.svg │ ├── webflow.svg │ ├── wix.svg │ └── woocommerce.svg ├── styles.css ├── theme.config.js ├── theme ├── InputWrapperStyles.js ├── extendTheme.js ├── globalStyles.js └── styles.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This configuration file was automatically generated by Gitpod. 2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) 3 | # and commit this file to your remote git repository to share the goodness with others. 4 | 5 | tasks: 6 | - init: yarn install && yarn run build 7 | command: yarn run start 8 | 9 | 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | public/ 4 | create-custom-channel.mdx 5 | create-custom-shop.mdx -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Openship documentation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components/Callout.js: -------------------------------------------------------------------------------- 1 | import { Notification, useMantineTheme } from "@mantine/core"; 2 | 3 | export function Callout({ color, title, body, mt = "lg" }) { 4 | const theme = useMantineTheme(); 5 | return ( 6 | 24 | {body} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /components/ChannelCards.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Stack, 4 | Modal, 5 | Accordion, 6 | Text, 7 | Divider, 8 | Code, 9 | Alert, 10 | List, 11 | Group, 12 | Button, 13 | Collapse, 14 | Box, 15 | useMantineTheme, 16 | Badge, 17 | Drawer, 18 | } from "@mantine/core"; 19 | import { useSSG } from "nextra/ssg"; 20 | import { PlatformCard } from "./PlatformCard"; 21 | import { PlatformAdder } from "./PlatformAdder"; 22 | import { IconAlertCircle } from "@tabler/icons"; 23 | import { Prism } from "@mantine/prism"; 24 | import { Callout } from "./Callout"; 25 | import { Papercups } from "@papercups-io/chat-widget"; 26 | import { useRouter } from "next/router"; 27 | import { useEffect } from "react"; 28 | import Image from "next/image"; 29 | import { useTheme } from "next-themes"; 30 | 31 | export const ChannelCards = () => { 32 | const { channels } = useSSG(); 33 | const [channelIndex, setChannelIndex] = useState(null); 34 | const [showAlert, setShowAlert] = useState(true); 35 | const theme = useMantineTheme(); 36 | const { theme: themeValue, setTheme, systemTheme } = useTheme(); 37 | 38 | const router = useRouter(); 39 | 40 | useEffect(() => { 41 | if (!router.isReady || !router.query.install) return; 42 | const paramChannel = channels.findIndex( 43 | ({ type }) => type === router.query.install 44 | ); 45 | if (paramChannel !== "-1") { 46 | setChannelIndex(paramChannel); 47 | } 48 | }, [router.isReady, router.query.install]); 49 | 50 | const InstallationGuides = { 51 | Shopify: ( 52 | 53 | ({ 56 | label: { fontSize: 16, fontWeight: 400, lineHeight: 1.2 }, 57 | item: { fontSize: 14 }, 58 | // itemOpened: { 59 | // backgroundColor: 60 | // theme.colors.gray[theme.colorScheme === "dark" ? 9 : 0], 61 | // }, 62 | })} 63 | > 64 | 65 | When adding the channel on Openship, choose SHOPIFY as 66 | the type and click Connect Shopify: 67 | 84 | This will take you to Shopify to install Openship. 85 | } 87 | title={ 88 | 89 | Openship's App Status 90 | 101 | 102 | } 103 | color="red" 104 | mt="xl" 105 | size="lg" 106 | pb={5} 107 | // variant="outline" 108 | sx={{ 109 | border: `1px solid ${ 110 | theme.colors.red[theme.colorScheme === "dark" ? 4 : 6] 111 | }`, 112 | boxShadow: theme.shadows.xs, 113 | }} 114 | styles={{ label: { width: "100%" } }} 115 | > 116 | 117 | When you install Openship on Shopify, you'll notice the app is 118 | unlisted. 119 |
120 |
121 | This is due to our pricing model. Shopify wants us to charge a 122 | fee for each Shopify channel our users connect. Openship charges 123 | 1 fee, allows unlimited channels, and supports e-commerce 124 | platforms outside of just Shopify. There are a pletora of apps 125 | in their app store that follow the same pricing model as us, but 126 | the playing field is not even. 127 |
128 |
129 | This wasn't an issue since unlisted apps could still be 130 | installed, but Shopify is removing this ability. They're forcing 131 | all apps to be installed from their App Store, adhere to their 132 | set pricing structure, while making exceptions for some apps 133 | along the way. 134 |
135 |
136 | Thankfully, installing this application is not the only way to 137 | use Shopify on Openship. You can create a custom app on your 138 | shop admin or create an app on the Shopify Partner dashboard. 139 | Read below to learn how. 140 |
141 |
142 |
143 | 147 | To create a custom app on Shopify, go to: 148 |
149 |
150 | https://your-domain.myshopify.com/admin/settings/apps 151 |
152 |
153 | And follow the video below: 154 | 162 |
163 |
164 | When setting the API scopes, these are the ones Openship needs 165 | access to: 166 |
167 | 168 | {[ 169 | "read_orders", 170 | "write_orders", 171 | "read_products", 172 | "write_products", 173 | "read_fulfillments", 174 | "write_fulfillments", 175 | "write_draft_orders", 176 | "read_draft_orders", 177 | "read_assigned_fulfillment_orders", 178 | "write_assigned_fulfillment_orders", 179 | "read_merchant_managed_fulfillment_orders", 180 | "write_merchant_managed_fulfillment_orders", 181 | ].map((scope) => ( 182 | 183 | {scope} 184 | 185 | ))} 186 | 187 |
188 | Once you have the Admin API access token copied, add a channel on 189 | Openship, choose 190 | SHOPIFY CUSTOM as the channel type, choose a channel 191 | name, put your shopify domain as the domain, and Admin API access 192 | token under access token. 193 | 220 |
221 | 224 | Create a custom app on the Shopify Partner dashboard and add the 225 | credentials to .env file 226 | 227 | } 228 | > 229 | Take it one step further and allow users to install your{" "} 230 | Shopify app when they add a Shopify channel on Openship. 231 |
232 |
233 | To accomplish this, you'll need a free{" "} 234 | 239 | Shopify Partners 240 | {" "} 241 | account. After creating one, you'll need to create an app on the 242 | dashboard and get the API key and API secret key. You'll need to add 243 | this to your .env file as{" "} 244 | CHANNEL_SHOPIFY_API_KEY and{" "} 245 | CHANNEL_SHOPIFY_SECRET respectively. 246 | 247 | 248 | {`CHANNEL_SHOPIFY_API_KEY=API_key 249 | CHANNEL_SHOPIFY_SECRET=API_secret_key`} 250 | 251 | 252 | 257 | If you're using Openship Cloud,{" "} 258 | 265 | get in touch 266 | {" "} 267 | and we'll add these variables to your instance. 268 | 269 | } 270 | /> 271 |
272 |
273 |
274 | ), 275 | BigCommerce: ( 276 | 277 | {/* 278 | Adding a Shopify shop to Openship can be done in 3 ways: 279 | 280 | */} 281 | ({ 284 | label: { fontSize: 16, fontWeight: 400, lineHeight: 1.2 }, 285 | item: { fontSize: 14 }, 286 | // itemOpened: { 287 | // backgroundColor: 288 | // theme.colors.gray[theme.colorScheme === "dark" ? 9 : 0], 289 | // }, 290 | })} 291 | > 292 | 293 | When adding the channel on Openship, choose BIGCOMMERCE{" "} 294 | as the type and click Connect BigCommerce: 295 | 313 | This will take you to BigCommerce to install Openship. 314 | 315 | 318 | Create a custom app on the BigCommerce Developer dashboard and 319 | add the credentials to .env file 320 | 321 | } 322 | > 323 | Take it one step further and allow users to install your{" "} 324 | BigCommerce app when they add a BigCommerce channel on your Openship 325 | instance. 326 |
327 |
328 | To accomplish this, you'll need a free{" "} 329 | 334 | BigCommerce Partner 335 | {" "} 336 | account. After creating one, you'll need to create an app on the 337 | dashboard and get the API key and API secret key. You'll need to add 338 | this to your .env file as{" "} 339 | CHANNEL_BIGCOMMERCE_API_KEY and{" "} 340 | CHANNEL_BIGCOMMERCE_SECRET respectively. 341 | 342 | 343 | {`CHANNEL_BIGCOMMERCE_API_KEY=API_key 344 | CHANNEL_BIGCOMMERCE_SECRET=API_secret_key`} 345 | 346 | 347 | 352 | If you're using Openship Cloud,{" "} 353 | 360 | get in touch 361 | {" "} 362 | and we'll add these variables to your instance. 363 | 364 | } 365 | /> 366 |
367 |
368 |
369 | ), 370 | }; 371 | 372 | return ( 373 | <> 374 | setChannelIndex(null)} 377 | title={ 378 | 379 | 385 | {channels[channelIndex]?.type}{" "} 386 | 387 | 388 | Installation Guide 389 | 390 | {/* 391 | 3 Options 392 | */} 393 | 394 | } 395 | size="lg" 396 | position="right" 397 | padding="md" 398 | styles={{ 399 | root: { overflow: "scroll" }, 400 | header: { 401 | marginLeft: -16, 402 | marginRight: -16, 403 | marginBottom: 0, 404 | marginTop: -16, 405 | padding: 16, 406 | background: themeValue === "light" ? "#ffffff" : "#000000", 407 | borderBottom: `1px solid ${ 408 | themeValue === "light" 409 | ? theme.colors.gray[3] 410 | : theme.colors.gray[8] 411 | }`, 412 | }, 413 | drawer: { overflow: "scroll" }, 414 | }} 415 | > 416 | {InstallationGuides[channels[channelIndex]?.type]} 417 | 418 | 419 | 420 | {channels.map((channel, index) => ( 421 | channel.stage === "DONE" && setChannelIndex(index)} 426 | /> 427 | ))} 428 | 429 | 430 | ); 431 | }; 432 | -------------------------------------------------------------------------------- /components/ChannelDiagram.js: -------------------------------------------------------------------------------- 1 | import { 2 | Badge, 3 | Box, 4 | Divider, 5 | Group, 6 | Paper, 7 | ThemeIcon, 8 | useMantineTheme, 9 | Center, 10 | Space, 11 | Stack, 12 | } from "@mantine/core"; 13 | import { IssueReopenedIcon } from "@primer/octicons-react"; 14 | import { Bicycle } from "iconoir-react"; 15 | import { Connector } from "./Connector"; 16 | import { OrderCard } from "./OrderCard"; 17 | 18 | export const ChannelDiagram = ({ activeImg }) => { 19 | const theme = useMantineTheme(); 20 | 21 | return ( 22 |
23 | 24 | 25 | 44 | 45 | 46 | ), 47 | }, 48 | ]} 49 | /> 50 | 51 | 70 | 71 | 72 | ), 73 | }, 74 | ]} 75 | /> 76 | 77 | 78 | , 88 | }, 89 | { 90 | name: "Tires", 91 | sku: "93443781", 92 | color: "green", 93 | icon: ( 94 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ), 109 | }, 110 | ]} 111 | /> 112 | 113 | 114 |
115 | ); 116 | }; 117 | -------------------------------------------------------------------------------- /components/Connector.js: -------------------------------------------------------------------------------- 1 | import { Box, Group, ThemeIcon, useMantineTheme } from "@mantine/core"; 2 | 3 | export function Connector({ degrees, icon, bg = "#0f1134" }) { 4 | const theme = useMantineTheme(); 5 | 6 | return ( 7 | 8 | 20 | 26 | 35 | {icon} 36 | 37 | {/* 47 | Match 48 | */} 49 | 50 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /components/Graypaper.js: -------------------------------------------------------------------------------- 1 | import { Paper, useMantineTheme } from "@mantine/core"; 2 | 3 | export const GrayPaper = ({ children, sx, p = "md", ...rest }) => { 4 | const theme = useMantineTheme(); 5 | return ( 6 | ({ 12 | backgroundColor: 13 | theme.colorScheme === "dark" 14 | ? theme.colors.dark[8] 15 | : theme.fn.lighten(theme.colors.gray[0], 0.4), 16 | color: 17 | theme.colorScheme === "dark" 18 | ? theme.colors.dark[3] 19 | : theme.colors.gray[7], 20 | ...sx, 21 | })} 22 | {...rest} 23 | > 24 | {children} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /components/IntroDiagram.js: -------------------------------------------------------------------------------- 1 | import { Box, Group, useMantineTheme, Space } from "@mantine/core"; 2 | import { OrderCard } from "./OrderCard"; 3 | 4 | export const IntroDiagram = ({ activeImg }) => { 5 | const theme = useMantineTheme(); 6 | 7 | return ( 8 | 30 | 49 | 50 | 51 | ), 52 | }, 53 | { 54 | name: "Bike Pump", 55 | sku: "72384762", 56 | color: "violet", 57 | icon: ( 58 | 67 | 68 | 69 | ), 70 | }, 71 | ]} 72 | /> 73 | 80 | 81 | 82 | {/* */} 94 | 106 | 107 | 108 | 109 | 110 | {/* */} 122 | 134 | {" "} 135 | 136 | 137 | 138 | 157 | 158 | 159 | ), 160 | }, 161 | ]} 162 | /> 163 | 171 | 190 | 191 | 192 | ), 193 | }, 194 | ]} 195 | /> 196 | 197 | 198 | ); 199 | }; 200 | -------------------------------------------------------------------------------- /components/KeyPopper.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | Box, 4 | Group, 5 | ActionIcon, 6 | useMantineTheme, 7 | Popper, 8 | Paper, 9 | Code, 10 | Button, 11 | Text, 12 | Center, 13 | } from "@mantine/core"; 14 | import crypto from "crypto"; 15 | import { KeyIcon, CopyIcon, SyncIcon, CheckIcon } from "@primer/octicons-react"; 16 | import { useClipboard } from "@mantine/hooks"; 17 | 18 | export function KeyPopper({ buttonColor, buttonBorder }) { 19 | const [referenceElement, setReferenceElement] = useState(null); 20 | const [value, setValue] = useState(null); 21 | const [visible, setVisible] = useState(true); 22 | const theme = useMantineTheme(); 23 | const clipboard = useClipboard({ timeout: 1000 }); 24 | 25 | async function createKey() { 26 | setValue(crypto.randomBytes(8).toString("hex")); 27 | } 28 | 29 | return ( 30 |
31 | setVisible((m) => !m)} 44 | type="unset" 45 | > 46 | 47 | 48 | 49 | 57 | 69 | {value ? ( 70 | 71 | 72 | 73 | {value} 74 | clipboard.copy(value)} 90 | > 91 | {clipboard.copied ? ( 92 | 93 | ) : ( 94 | 95 | )} 96 | 97 | 98 | 99 | 100 | await createKey()} 114 | 115 | // onClick={() => 116 | // modals.openConfirmModal({ 117 | // title: ( 118 | // 130 | // Regenerate Key 131 | // 132 | // ), 133 | // centered: true, 134 | // children: ( 135 | // 136 | // Are you sure you want to regenerate this key? This 137 | // action will invalidate your previous key and any 138 | // applications using the previous key will need to be 139 | // updated. 140 | // 141 | // ), 142 | // labels: { 143 | // confirm: "Regenerate Key", 144 | // cancel: "No don't regenerate it", 145 | // }, 146 | // confirmProps: { color: "red" }, 147 | // // onCancel: () => console.log("Cancel"), 148 | // onConfirm: createKey, 149 | // }) 150 | // } 151 | > 152 | 153 | 154 | 155 | ) : ( 156 | 165 | )} 166 | 167 | {/* setVisible((m) => !m)} 180 | > 181 | 182 | */} 183 | 184 | 185 |
186 | ); 187 | } 188 | -------------------------------------------------------------------------------- /components/MatchDiagram.js: -------------------------------------------------------------------------------- 1 | import { 2 | Badge, 3 | Box, 4 | Divider, 5 | Group, 6 | Paper, 7 | ThemeIcon, 8 | useMantineTheme, 9 | Space, 10 | } from "@mantine/core"; 11 | import { IssueReopenedIcon } from "@primer/octicons-react"; 12 | import { Connector } from "./Connector"; 13 | import { OrderCard } from "./OrderCard"; 14 | 15 | export const MatchDiagram = ({ activeImg }) => { 16 | const theme = useMantineTheme(); 17 | 18 | return ( 19 | 41 | 60 | 61 | 62 | ), 63 | }, 64 | { 65 | name: "Bike Pump", 66 | sku: "72384762", 67 | color: "violet", 68 | icon: ( 69 | 78 | 79 | 80 | ), 81 | }, 82 | ]} 83 | /> 84 | 91 | 101 | 105 | Match 106 | 107 | 108 | 115 | 124 | 125 | 126 | 127 | 132 | 139 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | } /> 156 | 157 | 158 | } /> 159 | 160 | 170 | 171 | 178 | 187 | 188 | 189 | 190 | 195 | 202 | 211 | 212 | 213 | 214 | 215 | 219 | Match 220 | 221 | 222 | 223 | 224 | 243 | 244 | 245 | ), 246 | }, 247 | ]} 248 | /> 249 | 257 | 276 | 277 | 278 | ), 279 | }, 280 | ]} 281 | /> 282 | 283 | 284 | ); 285 | }; 286 | -------------------------------------------------------------------------------- /components/OrderCard.js: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Group, 4 | Paper, 5 | Stack, 6 | Text, 7 | ThemeIcon, 8 | useMantineTheme, 9 | } from "@mantine/core"; 10 | import { GrayPaper } from "./Graypaper"; 11 | 12 | export function OrderCard({ title, order, lineItems, tracking, color }) { 13 | const theme = useMantineTheme(); 14 | 15 | return ( 16 | 17 | 25 | 26 | 33 | 34 | 35 | palette.purple[400], 44 | fontWeight: 600, 45 | letterSpacing: "0.06rem", 46 | width: "100%", 47 | color: theme.colors[color][6], 48 | fontSize: 12, 49 | [theme.fn.largerThan("xs")]: { 50 | fontSize: 16, 51 | }, 52 | }} 53 | > 54 | {title} 55 | 56 | 72 | Order {order} 73 | 74 | 75 | 76 | 77 | {lineItems.map(({ name, sku, icon, color }) => ( 78 | 89 | 90 | 110 | {icon} 111 | 112 | 113 | 121 | {name} 122 | 123 | 133 | SKU: {sku} 134 | 135 | 136 | 137 | 138 | ))} 139 | 140 | 141 | {tracking && tracking.map((a) => a)} 142 | 143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /components/PlatformAdder.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { 3 | Text, 4 | useMantineTheme, 5 | Box, 6 | Select, 7 | Modal, 8 | Paper, 9 | Center, 10 | Divider, 11 | Button, 12 | TextInput, 13 | Stack, 14 | } from "@mantine/core"; 15 | 16 | export const PlatformAdder = ({ 17 | title = "Channel", 18 | PlatformForms = { 19 | demo: { 20 | label: "Demo", 21 | fields: [ 22 | { 23 | title: "Name", 24 | name: "name", 25 | placeholder: `Demo ${title}`, 26 | }, 27 | ], 28 | buttonText: `Create ${title}`, 29 | }, 30 | }, 31 | maxWidth = 250, 32 | }) => { 33 | const theme = useMantineTheme(); 34 | const [type, setType] = useState(Object.keys(PlatformForms)[0]); 35 | 36 | return ( 37 |
38 | 39 | 53 | Create {title} 54 | 55 | 56 | 57 |