├── .arc ├── .gitignore └── app ├── .gitignore ├── .vscode └── settings.json ├── components ├── GenericTable.tsx ├── Labels.tsx ├── Layout.tsx ├── OrderTable.tsx ├── OrderVariations.tsx └── Scanner.tsx ├── hooks ├── useBatch.ts ├── useFocus.tsx └── useShipment.ts ├── interfaces ├── chitchat.d.ts ├── index.ts └── snipcart.d.ts ├── next-env.d.ts ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ ├── auth │ │ └── [...nextauth].ts │ ├── batches │ │ ├── [id].ts │ │ └── index.ts │ ├── orders │ │ ├── [token].ts │ │ ├── index.ts │ │ └── refetch-metadata.ts │ ├── products │ │ └── index.ts │ ├── shipments │ │ ├── [id].ts │ │ ├── add_to_batch.ts │ │ └── index.ts │ ├── shipping │ │ └── index.ts │ ├── snipcart │ │ └── [...proxy].ts │ ├── test │ │ └── index.ts │ └── webhook │ │ └── index.ts ├── auth │ └── index.tsx ├── batches │ ├── [batchId].tsx │ └── index.tsx ├── index.tsx └── orders.tsx ├── public └── beep.wav ├── readme.md ├── tsconfig.json └── utils ├── chitchats.ts ├── getShippingQuote.ts ├── initMiddleware.ts ├── snipCart.ts ├── snipCartAPI.ts ├── stallion.ts └── withAuth.ts /.arc: -------------------------------------------------------------------------------- 1 | @app 2 | chit-chats 3 | 4 | @http 5 | get / 6 | /shipping 7 | method get 8 | src src/http/get-shipping 9 | /shipping 10 | method post 11 | src src/http/get-shipping 12 | post /webhook 13 | 14 | @aws 15 | # profile default 16 | # region us-west-1 17 | timeout 55 18 | 19 | @tables 20 | data 21 | scopeID *String 22 | dataID **String 23 | ttl TTL 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | haters 2 | .DS_Store 3 | dist/ 4 | /src/http/**/*.js 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # Snowpack dependency directory (https://snowpack.dev/) 50 | web_modules/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | .parcel-cache 83 | 84 | # Next.js build output 85 | .next 86 | out 87 | 88 | # Nuxt.js build / generate output 89 | .nuxt 90 | dist 91 | 92 | # Gatsby files 93 | .cache/ 94 | # Comment in the public line in if your project uses Gatsby and not Next.js 95 | # https://nextjs.org/blog/next-9-1#public-directory-support 96 | # public 97 | 98 | # vuepress build output 99 | .vuepress/dist 100 | 101 | # Serverless directories 102 | .serverless/ 103 | 104 | # FuseBox cache 105 | .fusebox/ 106 | 107 | # DynamoDB Local files 108 | .dynamodb/ 109 | 110 | # TernJS port file 111 | .tern-port 112 | 113 | # Stores VSCode versions used for testing VSCode extensions 114 | .vscode-test 115 | 116 | # yarn v2 117 | .yarn/cache 118 | .yarn/unplugged 119 | .yarn/build-state.yml 120 | .yarn/install-state.gz 121 | .pnp.* 122 | 123 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 124 | 125 | # dependencies 126 | /node_modules 127 | /.pnp 128 | .pnp.js 129 | 130 | # testing 131 | /coverage 132 | 133 | # next.js 134 | /.next/ 135 | /out/ 136 | 137 | # production 138 | /build 139 | 140 | # misc 141 | .DS_Store 142 | *.pem 143 | 144 | # debug 145 | npm-debug.log* 146 | yarn-debug.log* 147 | yarn-error.log* 148 | 149 | # local env files 150 | .env.local 151 | .env.development.local 152 | .env.test.local 153 | .env.production.local 154 | 155 | # vercel 156 | .vercel 157 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.env.* 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | -------------------------------------------------------------------------------- /app/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "snipcart" 4 | ] 5 | } -------------------------------------------------------------------------------- /app/components/GenericTable.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const TableStyles = styled.table` 4 | width: 100%; 5 | font-family: sans-serif; 6 | border-collapse: collapse; 7 | td { 8 | padding: 3px; 9 | border: 1px solid #ededed; 10 | } 11 | th { 12 | text-align: left; 13 | background: black; 14 | color white; 15 | } 16 | tr:nth-child(even) { 17 | background: #efefef; 18 | } 19 | `; 20 | type GenericTableProps = { 21 | data: any[]; 22 | columns: string[]; 23 | }; 24 | 25 | export default function GenericTable({ 26 | data = [], 27 | columns = [], 28 | }: GenericTableProps) { 29 | return ( 30 | <> 31 | 32 | 33 | 34 | {columns.map((heading) => ( 35 | {heading} 36 | ))} 37 | 38 | 39 | 40 | {data.map((item) => ( 41 | 42 | {columns.map((heading) => ( 43 | 44 | {item[heading]} 45 | 46 | ))} 47 | 48 | ))} 49 | 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /app/components/Labels.tsx: -------------------------------------------------------------------------------- 1 | import QRCode from 'qrcode.react'; 2 | import { Fragment } from 'react'; 3 | import styled from 'styled-components'; 4 | import { SnipCartOrder } from '../interfaces/snipcart'; 5 | 6 | export const LabelStyles = styled.div` 7 | width: 4in; 8 | height: 6in; 9 | margin: 20px 0; 10 | @media print { 11 | width: 100%; 12 | height: 100vh; 13 | margin: 0; 14 | border: 0; 15 | } 16 | font-family: 'Operator Mono'; 17 | border: 4px solid black; 18 | img { 19 | width: 100%; 20 | height: 100%; 21 | } 22 | p { 23 | margin: 0; 24 | margin-bottom: 5px; 25 | } 26 | .label-header { 27 | display: grid; 28 | grid-template-columns: 1fr auto; 29 | justify-content: center; 30 | align-items: center; 31 | text-align: left; 32 | background: black; 33 | color: white; 34 | font-style: italic; 35 | font-size: 15px; 36 | padding-left: 20px; 37 | canvas { 38 | width: 100%; 39 | border: 2px solid white; 40 | } 41 | h2 { 42 | margin: 0; 43 | font-size: 1rem; 44 | } 45 | } 46 | ol { 47 | list-style-type: decimal-leading-zero; 48 | margin: 0; 49 | li { 50 | border-bottom: 1px solid black; 51 | padding: 2px; 52 | } 53 | } 54 | h3 { 55 | margin: 0; 56 | margin-bottom: 5px; 57 | } 58 | .label-footer { 59 | background: black; 60 | color: white; 61 | text-align: center; 62 | text-transform: uppercase; 63 | p { 64 | margin: 2px; 65 | } 66 | } 67 | 68 | .meta { 69 | display: flex; 70 | font-size: 13px; 71 | font-family: --apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 72 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 73 | gap: 10px; 74 | } 75 | &.packing { 76 | display: grid; 77 | grid-template-rows: auto 1fr auto; 78 | } 79 | &.shipping { 80 | display: grid; 81 | grid-template-rows: 50px 1fr; 82 | img { 83 | height: 100%; 84 | min-height: 0; 85 | } 86 | canvas { 87 | border: 2px solid white; 88 | } 89 | } 90 | `; 91 | 92 | const LabelsGrid = styled.div` 93 | & > div { 94 | display: grid; 95 | grid-template-columns: 1fr 1fr; 96 | @media print { 97 | display: block; 98 | } 99 | } 100 | `; 101 | 102 | function PackingList({ order }: { order: SnipCartOrder }) { 103 | const { items } = order; 104 | return ( 105 | 106 |
107 |

108 | Order {order.invoiceNumber} 109 |
110 | Shipment {order?.metadata?.chitChatId} 111 |
112 | {order.billingAddressName} 113 |

114 | 115 |
116 |
    117 | {items?.map((item, i) => ( 118 |
  1. 119 |

    {item.name}

    120 | 121 |

    122 | Qty: {item.quantity} 123 |

    124 |

    125 | {item.customFields?.map((field) => ( 126 | 127 | {field.name}:{' '} 128 | {field.displayValue} 129 | 130 | ))} 131 |

    132 |

    133 | ID: {item.id} 134 |

    135 |
    136 |
  2. 137 | ))} 138 |

    139 | {order.numberOfItemsInOrder} Item 140 | {order.numberOfItemsInOrder === 1 ? '' : 's'} Total 141 |

    142 |
143 |
144 |

Thank You × You are a good dev × Wes Bos

145 |
146 |
147 | ); 148 | } 149 | 150 | function ShippingLabel({ order }: { order: SnipCartOrder }) { 151 | return ( 152 | 153 | 154 | 155 | 156 | ); 157 | } 158 | 159 | type OrdersProps = { 160 | orders?: SnipCartOrder[]; 161 | }; 162 | 163 | export function Labels({ orders }: OrdersProps) { 164 | if (!orders) return

No orders to show

; 165 | return ( 166 | 167 | {orders.map((order) => ( 168 |
169 | 170 | 171 |
172 | ))} 173 |
174 | ); 175 | } 176 | -------------------------------------------------------------------------------- /app/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import Link from 'next/link'; 3 | import Head from 'next/head'; 4 | import styled, { createGlobalStyle } from 'styled-components'; 5 | import 'normalize.css'; 6 | import { useIsFetching } from 'react-query'; 7 | import nProgress from 'nprogress'; 8 | import { useSession } from 'next-auth/client'; 9 | import { Scanner } from './Scanner'; 10 | 11 | type Props = { 12 | children?: ReactNode; 13 | title?: string; 14 | }; 15 | 16 | const GlobalStyles = createGlobalStyle` 17 | :root { 18 | --black: black; 19 | --yellow: #ffc600; 20 | --light: #ffffff; 21 | --dark: #000000; 22 | --lightGrey: #d8d8d8; 23 | --backgroundGrey: #f7f7f7; 24 | --lightGray: var(--lightGrey); 25 | --imGoingToFaint: #fbfbfb; 26 | --maxWidth: 1200px; 27 | } 28 | 29 | html { 30 | box-sizing: border-box; 31 | font-family: 'Operator Mono', --apple-system, BlinkMacSystemFont, 'Segoe UI', 32 | Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', 33 | sans-serif; 34 | } 35 | a { 36 | text-decoration-color: var(--yellow); 37 | color: var(--black); 38 | } 39 | button { 40 | background: var(--yellow); 41 | border: 0; 42 | padding: 5px; 43 | border-radius: 2px; 44 | } 45 | *, 46 | &::before, 47 | *::after { 48 | box-sizing: inherit; 49 | } 50 | 51 | @media print { 52 | header, 53 | footer { 54 | display: none; 55 | } 56 | @page { 57 | size: 4in 6in; 58 | margin: 0; 59 | } 60 | .no-print { 61 | display: none; 62 | } 63 | } 64 | // Base Table styles 65 | table { 66 | width: 100%; 67 | border: 1px solid black; 68 | font-family: sans-serif; 69 | img { 70 | float: left; 71 | border-radius: 50%; 72 | margin-right: 10px; 73 | } 74 | td { 75 | padding: 2px; 76 | vertical-align: middle; 77 | max-width: 150px; 78 | overflow: scroll; 79 | text-overflow: ellipsis; 80 | } 81 | } 82 | tr:nth-child(even) { 83 | background: var(--backgroundGrey); 84 | } 85 | 86 | // nprogress 87 | #nprogress { 88 | @media print { 89 | display: none; 90 | } 91 | .bar { 92 | height: 4px; 93 | background: var(--yellow); 94 | } 95 | .peg { 96 | box-shadow: 0 0 10px var(--yellow), 0 0 5px var(--yellow); 97 | } 98 | .spinner-icon { 99 | border-top-color: var(--yellow); 100 | border-left-color: var(--yellow); 101 | } 102 | } 103 | 104 | .blurred .blur { 105 | filter: blur(4px); 106 | } 107 | .blurred span.blur { 108 | filter: blur(3px); 109 | } 110 | `; 111 | 112 | const LayoutStyles = styled.div` 113 | display: grid; 114 | min-height: 100vh; 115 | grid-template-rows: auto 1fr auto; 116 | max-width: 1000px; 117 | margin: 0 auto; 118 | border: 5px solid var(--black); 119 | padding: 2rem; 120 | @media print { 121 | border: 0; 122 | min-height: none; 123 | padding: 0; 124 | } 125 | `; 126 | 127 | const Logo = styled.h1` 128 | display: grid; 129 | grid-auto-flow: column; 130 | align-items: center; 131 | justify-content: start; 132 | gap: 2rem; 133 | `; 134 | 135 | const Nav = styled.nav` 136 | display: grid; 137 | grid-template-columns: auto auto auto 1fr; 138 | gap: 1rem; 139 | align-items: center; 140 | `; 141 | 142 | export default function Layout({ 143 | children, 144 | title = 'This is the default title', 145 | }: Props) { 146 | const isFetching = useIsFetching(); 147 | if (isFetching) { 148 | nProgress.start(); 149 | } else { 150 | nProgress.done(); 151 | } 152 | const [session, loading] = useSession(); 153 | if (loading) return

Loading..

; 154 | // dont worry we also lock down the data in the API endpoints, this is just visual. 155 | if (!session) 156 | return ( 157 |
158 |

VERCEL_ENV: {process.env.VERCEL_ENV}

159 |

NODE_ENV: {process.env.NODE_ENV}

160 | 161 | Login 162 | 163 |
164 | ); 165 | return ( 166 | 167 | 168 | 169 | 170 | {isFetching} {title} 171 | 172 | 173 | 174 | 175 |
176 | 177 | 182 | Swag Shop. 183 | 184 | 190 |
191 |
{children}
192 |