├── .babelrc.json ├── .editorconfig ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ci.yml │ └── deployment.yml ├── .gitignore ├── .linguirc ├── .npmrc ├── .nvmrc ├── .prettierrc ├── LICENSE ├── README.md ├── config ├── gatsby-config.ts ├── helpers │ └── markdown.ts └── plugins │ └── capitalize.ts ├── gatsby-config.js ├── gatsby-node.ts ├── package.json ├── src ├── Root.tsx ├── assets │ ├── fonts │ │ ├── MyIcons.eot │ │ ├── MyIcons.svg │ │ ├── MyIcons.ttf │ │ ├── MyIcons.woff │ │ └── MyIcons.woff2 │ └── images │ │ ├── banners │ │ ├── black.png │ │ ├── blue.png │ │ ├── dark-blue.png │ │ ├── green.png │ │ ├── light-blue.png │ │ └── light-green.png │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logos │ │ ├── coinbase.svg │ │ ├── ledger.svg │ │ ├── quicknode.svg │ │ ├── trezor.svg │ │ └── unstoppable-domains.svg │ │ ├── membership.svg │ │ └── sad-wallet.svg ├── components │ ├── Article.tsx │ ├── Articles.tsx │ ├── Banner │ │ ├── Banner.tsx │ │ ├── Banners │ │ │ ├── CoinbaseBanner.tsx │ │ │ ├── LedgerBanner.tsx │ │ │ ├── MembershipBanner.tsx │ │ │ ├── QuickNodeBanner.tsx │ │ │ ├── TrezorBanner.tsx │ │ │ ├── UnstoppableDomainsBanner.tsx │ │ │ └── index.ts │ │ ├── RandomBanner.tsx │ │ ├── StaticRandomBanner.tsx │ │ └── index.ts │ ├── Card.tsx │ ├── Categories.tsx │ ├── Category.tsx │ ├── CategoryButton.tsx │ ├── Divider.tsx │ ├── Footer.tsx │ ├── Grid.tsx │ ├── Header.tsx │ ├── Hero.tsx │ ├── Label.tsx │ ├── Layout.tsx │ ├── Link.tsx │ ├── Markdown.tsx │ ├── Metadata.tsx │ ├── Page.tsx │ ├── Results.tsx │ ├── Search.tsx │ ├── Section.tsx │ ├── Sidebar.tsx │ ├── Troubleshooter.tsx │ ├── index.ts │ └── markdown │ │ ├── Accordion.tsx │ │ ├── Alert.tsx │ │ ├── Clearfix.tsx │ │ ├── TokenInputData.tsx │ │ ├── UnitConverter │ │ ├── Unit.tsx │ │ ├── UnitConverter.tsx │ │ └── index.ts │ │ ├── YouTubeEmbed.tsx │ │ ├── default │ │ ├── Blockquote.tsx │ │ ├── Code.tsx │ │ ├── DeletedText.tsx │ │ ├── EmphasizedText.tsx │ │ ├── H1.tsx │ │ ├── H2.tsx │ │ ├── H3.tsx │ │ ├── H4.tsx │ │ ├── H5.tsx │ │ ├── H6.tsx │ │ ├── Image.tsx │ │ ├── InlineCode.tsx │ │ ├── KeyboardInput.tsx │ │ ├── Link.tsx │ │ ├── List │ │ │ ├── List.tsx │ │ │ ├── ListItem.tsx │ │ │ ├── OrderedList.tsx │ │ │ ├── UnorderedList.tsx │ │ │ └── index.ts │ │ ├── Paragraph.tsx │ │ ├── PreformattedText.tsx │ │ ├── StrongText.tsx │ │ ├── Table │ │ │ ├── Table.tsx │ │ │ ├── TableCell.tsx │ │ │ ├── TableHead.tsx │ │ │ ├── TableHeading.tsx │ │ │ ├── TableRow.tsx │ │ │ └── index.ts │ │ ├── ThematicBreak.tsx │ │ └── index.ts │ │ └── index.ts ├── config │ ├── articles.ts │ ├── index.ts │ ├── links.ts │ └── search.ts ├── hooks │ ├── index.ts │ ├── useElasticSearch.ts │ └── useSiteMetadata.ts ├── locales │ └── en │ │ └── messages.po ├── pages │ ├── 404.tsx │ ├── articles │ │ ├── latest.tsx │ │ └── popular.tsx │ ├── index.tsx │ ├── search.tsx │ ├── {Mdx.slug}.tsx │ └── {Yaml.slug}.tsx ├── theme.ts ├── types │ ├── breadcrumb.ts │ ├── category.ts │ ├── custom.d.ts │ ├── index.ts │ ├── page.ts │ ├── tag.ts │ ├── vendor │ │ ├── gatsby-plugin-mdx.d.ts │ │ ├── gatsby.d.ts │ │ ├── mdx-js │ │ │ └── react.d.ts │ │ ├── strip-markdown.d.ts │ │ └── styled-components.d.ts │ └── window.d.ts └── utils │ ├── index.ts │ └── social.ts ├── static └── vendor │ └── piwik.js ├── tsconfig.json └── yarn.lock /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "babel-preset-gatsby", 5 | { 6 | "reactRuntime": "automatic" 7 | } 8 | ], 9 | "@babel/preset-typescript", 10 | "@lingui/babel-preset-react" 11 | ], 12 | "env": { 13 | "test": { 14 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": ["eslint:recommended", "eslint-config-prettier"], 5 | "plugins": ["eslint-plugin-react", "eslint-plugin-import"], 6 | "rules": { 7 | "comma-dangle": ["error", "never"], 8 | "curly": "error", 9 | "import/first": "error", 10 | "import/newline-after-import": "error", 11 | "import/no-deprecated": "warn", 12 | "import/no-duplicates": "error", 13 | "import/no-mutable-exports": "error", 14 | "import/no-namespace": "error", 15 | "import/no-self-import": "error", 16 | "import/no-useless-path-segments": "error", 17 | "import/order": [ 18 | "error", 19 | { 20 | "newlines-between": "never", 21 | "alphabetize": { 22 | "order": "asc", 23 | "caseInsensitive": false 24 | } 25 | } 26 | ], 27 | "no-console": "error", 28 | "quotes": ["error", "single"] 29 | }, 30 | "overrides": [ 31 | { 32 | "files": ["*.ts", "*.tsx"], 33 | "extends": [ 34 | "plugin:@typescript-eslint/recommended", 35 | "plugin:eslint-plugin-react/recommended", 36 | "plugin:eslint-plugin-react-hooks/recommended", 37 | "plugin:eslint-plugin-import/typescript" 38 | ], 39 | "plugins": ["@typescript-eslint"], 40 | "rules": { 41 | "@typescript-eslint/array-type": [ 42 | "error", 43 | { 44 | "default": "array-simple" 45 | } 46 | ], 47 | "@typescript-eslint/consistent-type-assertions": [ 48 | "error", 49 | { 50 | "assertionStyle": "as" 51 | } 52 | ], 53 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 54 | "@typescript-eslint/explicit-member-accessibility": [ 55 | "error", 56 | { 57 | "accessibility": "no-public" 58 | } 59 | ], 60 | "@typescript-eslint/method-signature-style": ["error", "method"], 61 | "@typescript-eslint/no-non-null-assertion": "off", 62 | "react/no-unescaped-entities": "off", 63 | "react/prop-types": "off", 64 | "react/react-in-jsx-scope": "off", 65 | "react-hooks/exhaustive-deps": "off" 66 | }, 67 | "settings": { 68 | "react": { 69 | "version": "detect" 70 | } 71 | }, 72 | "parserOptions": { 73 | "ecmaFeatures": { 74 | "jsx": true 75 | } 76 | } 77 | }, 78 | { 79 | "files": ["gatsby-config.js", "gatsby/**/*"], 80 | "env": { 81 | "node": true 82 | } 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug on the knowledge base 4 | --- 5 | 6 | 9 | 10 | ## Description 11 | 12 | 15 | 16 | ## Steps to reproduce 17 | 18 | 21 | 22 | 1. 23 | 2. 24 | 3. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for the knowledge base 4 | --- 5 | 6 | ## Summary 7 | 8 | 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '*' 7 | 8 | push: 9 | branches: 10 | - 'master' 11 | 12 | jobs: 13 | lint: 14 | name: Lint 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Read .nvmrc 21 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 22 | id: nvm 23 | 24 | - name: Setup Node.js ${{ steps.nvm.outputs.NVMRC }} 25 | uses: actions/setup-node@v2.1.5 26 | with: 27 | node-version: ${{ steps.nvm.outputs.NVMRC }} 28 | 29 | - name: Cache Dependencies 30 | uses: actions/cache@v2 31 | with: 32 | path: | 33 | node_modules 34 | key: ${{ hashFiles('yarn.lock') }}-${{ hashFiles('config/**/*') }} 35 | 36 | - name: Install Dependencies 37 | run: yarn install --frozen-lockfile 38 | 39 | - name: Compile Translations 40 | run: yarn compile 41 | 42 | - name: Lint 43 | run: yarn lint 44 | 45 | build: 46 | name: Build 47 | runs-on: ubuntu-latest 48 | 49 | steps: 50 | - uses: actions/checkout@v2 51 | 52 | - name: Read .nvmrc 53 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 54 | id: nvm 55 | 56 | - name: Setup Node.js ${{ steps.nvm.outputs.NVMRC }} 57 | uses: actions/setup-node@v2.1.5 58 | with: 59 | node-version: ${{ steps.nvm.outputs.NVMRC }} 60 | 61 | - name: Cache Dependencies 62 | uses: actions/cache@v2 63 | with: 64 | path: | 65 | node_modules 66 | key: ${{ hashFiles('yarn.lock') }}-${{ hashFiles('config/**/*') }} 67 | 68 | - name: Install Dependencies 69 | run: yarn install --frozen-lockfile 70 | 71 | - name: Compile Translations 72 | run: yarn compile 73 | 74 | - name: Build 75 | run: yarn build 76 | -------------------------------------------------------------------------------- /.github/workflows/deployment.yml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | deploy: 11 | name: Deploy 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Read .nvmrc 18 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 19 | id: nvm 20 | 21 | - name: Setup Node.js ${{ steps.nvm.outputs.NVMRC }} 22 | uses: actions/setup-node@v2.1.5 23 | with: 24 | node-version: ${{ steps.nvm.outputs.NVMRC }} 25 | 26 | - name: Cache Dependencies 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | node_modules 31 | key: ${{ hashFiles('yarn.lock') }}-${{ hashFiles('config/**/*') }} 32 | 33 | - name: Install Dependencies 34 | run: yarn install --frozen-lockfile 35 | 36 | - name: Compile Translations 37 | run: yarn compile 38 | 39 | - name: Lint 40 | run: yarn lint 41 | 42 | - name: Build 43 | run: yarn build 44 | env: 45 | ELASTIC_AWS_SYNC: true 46 | ELASTIC_AWS_ENDPOINT: ${{ secrets.ELASTIC_AWS_ENDPOINT }} 47 | ELASTIC_AWS_ACCESS_KEY_ID: ${{ secrets.ELASTIC_AWS_ACCESS_KEY_ID }} 48 | ELASTIC_AWS_SECRET_ACCESS_KEY: ${{ secrets.ELASTIC_AWS_SECRET_ACCESS_KEY }} 49 | 50 | - name: Deploy 51 | run: yarn deploy 52 | env: 53 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 54 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 55 | 56 | - name: Create Invalidation 57 | run: | 58 | aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} --paths "/*" 59 | env: 60 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 61 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | lib-cov 12 | coverage 13 | .nyc_output 14 | .grunt 15 | bower_components 16 | .lock-wscript 17 | build/Release 18 | node_modules/ 19 | jspm_packages/ 20 | typings/ 21 | .npm 22 | .eslintcache 23 | .node_repl_history 24 | *.tgz 25 | .yarn-integrity 26 | .env 27 | .next 28 | 29 | # Gatsby 30 | public/ 31 | .cache 32 | content/ 33 | src/locales/*/messages.ts 34 | src/locales/*/messages.d.ts 35 | -------------------------------------------------------------------------------- /.linguirc: -------------------------------------------------------------------------------- 1 | { 2 | "locales": [ 3 | "en" 4 | ], 5 | "catalogs": [ 6 | { 7 | "path": "src/locales/{locale}/messages", 8 | "include": [ 9 | "src" 10 | ] 11 | } 12 | ], 13 | "format": "po", 14 | "compileNamespace": "ts" 15 | } 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.18.1 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "bracketSpacing": true, 7 | "bracketSameLine": true, 8 | "endOfLine": "lf", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 MyCrypto 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyCrypto Knowledge Base 2 | 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 4 | 5 | This repository contains the code for the [MyCrypto Knowledge Base](https://support.mycrypto.com). For the Knowledge Base content, see [MyCryptoHQ/knowledge-base-content](https://github.com/MyCryptoHQ/knowledge-base-content). 6 | 7 | ## Getting Started 8 | 9 | ### Development 10 | 11 | To start a development server, run the following command 12 | 13 | ```bash 14 | yarn run start 15 | ``` 16 | 17 | The server will be accessible on `localhost:8000`. 18 | 19 | ### Production 20 | 21 | To build a static version of the Knowledge Base for production, run the following command 22 | 23 | ```bash 24 | yarn run build 25 | ``` 26 | 27 | The Knowledge Base is automatically deployed by [GitHub Actions](https://github.com/MyCryptoHQ/knowledge-base/blob/master/.github/workflows/deployment.yml) when tagged. 28 | 29 | ## License 30 | 31 | The Knowledge Base is [MIT licensed](./LICENSE). 32 | -------------------------------------------------------------------------------- /config/gatsby-config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { GatsbyConfig } from 'gatsby'; 3 | import { removeMarkdown } from './helpers/markdown'; 4 | import capitalize from './plugins/capitalize'; 5 | 6 | const config: GatsbyConfig = { 7 | flags: { 8 | FAST_DEV: true 9 | }, 10 | 11 | siteMetadata: { 12 | title: 'MyCrypto Knowledge Base', 13 | description: 14 | 'Answers & help for MyCrypto.com & the Ethereum blockchain. If your answer is not here, contact MyCrypto support instantly.', 15 | siteUrl: 'https://support.mycrypto.com', 16 | baseUrl: 'https://support.mycrypto.com', 17 | recaptchaSitekey: '6LcOl00UAAAAACVjGdVFkw918ohOhPIL0PHDtdGM' 18 | }, 19 | pathPrefix: '/', 20 | plugins: [ 21 | 'gatsby-plugin-react-helmet', 22 | 'gatsby-plugin-sitemap', 23 | 'gatsby-plugin-sharp', 24 | 'gatsby-plugin-catch-links', 25 | 'gatsby-plugin-styled-components', 26 | { 27 | resolve: 'gatsby-plugin-git-clone', 28 | options: { 29 | repository: 'https://github.com/MyCryptoHQ/knowledge-base-content', 30 | path: resolve(__dirname, '../content') 31 | } 32 | }, 33 | { 34 | resolve: 'gatsby-source-filesystem', 35 | options: { 36 | path: resolve(__dirname, '../content'), 37 | name: 'content', 38 | ignore: ['**/README.md', '**/.github/**', '**/redirects.yml'] 39 | } 40 | }, 41 | { 42 | resolve: 'gatsby-plugin-mdx', 43 | options: { 44 | extensions: ['.mdx', '.md'], 45 | gatsbyRemarkPlugins: [ 46 | { 47 | resolve: 'gatsby-remark-images', 48 | options: { 49 | maxWidth: 750 50 | } 51 | }, 52 | { 53 | resolve: 'gatsby-remark-copy-linked-files', 54 | options: { 55 | ignoreFileExtensions: ['png', 'jpg', 'jpeg'] 56 | } 57 | }, 58 | { 59 | resolve: 'gatsby-remark-external-links', 60 | options: { 61 | target: '_blank', 62 | rel: 'noopener noreferrer' 63 | } 64 | } 65 | ], 66 | plugins: [ 67 | { 68 | resolve: 'gatsby-remark-images', 69 | options: { 70 | maxWidth: 750 71 | } 72 | } 73 | ], 74 | remarkPlugins: [require('remark-kbd'), capitalize], 75 | rehypePlugins: [require('rehype-slug')] 76 | } 77 | }, 78 | { 79 | resolve: 'gatsby-transformer-yaml', 80 | options: { 81 | typeName: 'Yaml' 82 | } 83 | }, 84 | { 85 | resolve: 'gatsby-source-filesystem', 86 | options: { 87 | path: resolve(__dirname, '../src/assets/images'), 88 | name: 'images' 89 | } 90 | }, 91 | { 92 | resolve: 'gatsby-plugin-manifest', 93 | options: { 94 | icon: resolve(__dirname, '../src/assets/images/logo.svg'), 95 | name: 'MyCrypto Knowledge Base', 96 | short_name: 'MyCrypto KB', 97 | start_url: '/', 98 | background_color: '#1d334f' 99 | } 100 | }, 101 | { 102 | resolve: 'gatsby-plugin-matomo', 103 | options: { 104 | siteId: '3', 105 | matomoUrl: 'https://analytics.mycryptoapi.com', 106 | siteUrl: 'https://support.mycrypto.com/', 107 | disableCookies: true, 108 | localScript: '/vendor/piwik.js' 109 | } 110 | }, 111 | { 112 | resolve: 'gatsby-plugin-canonical-urls', 113 | options: { 114 | siteUrl: 'https://support.mycrypto.com' 115 | } 116 | }, 117 | { 118 | resolve: 'gatsby-plugin-s3', 119 | options: { 120 | bucketName: 'support.mycrypto.com', 121 | protocol: 'https', 122 | hostname: 'support.mycrypto.com', 123 | generateRedirectObjectsForPermanentRedirects: true 124 | } 125 | }, 126 | { 127 | resolve: 'gatsby-plugin-aws-elasticsearch', 128 | options: { 129 | enabled: !!process.env.ELASTIC_AWS_SYNC, 130 | query: ` 131 | query { 132 | allMdx (filter: { slug: { glob: "!troubleshooter/**" } }) { 133 | nodes { 134 | slug 135 | rawBody 136 | excerpt (pruneLength: 500) 137 | frontmatter { 138 | title 139 | tags 140 | } 141 | } 142 | } 143 | } 144 | `, 145 | 146 | selector: (data: { allMdx: { nodes: unknown[] } }): unknown[] => data.allMdx.nodes, 147 | toDocument: (node: Record): Record => ({ 148 | id: (node.slug as string).replace(/\//g, '-'), 149 | slug: node.slug, 150 | title: (node.frontmatter as Record).title, 151 | tags: (node.frontmatter as Record).tags, 152 | content: removeMarkdown(node.rawBody as string), 153 | excerpt: node.excerpt 154 | }), 155 | 156 | mapping: { 157 | slug: { 158 | type: 'keyword' 159 | }, 160 | title: { 161 | type: 'text', 162 | boost: 2 163 | }, 164 | tags: { 165 | type: 'text' 166 | }, 167 | content: { 168 | type: 'text' 169 | }, 170 | excerpt: { 171 | type: 'text', 172 | index: false 173 | } 174 | }, 175 | 176 | endpoint: process.env.ELASTIC_AWS_ENDPOINT, 177 | index: 'articles', 178 | 179 | accessKeyId: process.env.ELASTIC_AWS_ACCESS_KEY_ID, 180 | secretAccessKey: process.env.ELASTIC_AWS_SECRET_ACCESS_KEY 181 | } 182 | } 183 | ] 184 | }; 185 | 186 | export default config; 187 | -------------------------------------------------------------------------------- /config/helpers/markdown.ts: -------------------------------------------------------------------------------- 1 | import remark from 'remark'; 2 | import stripMarkdown from 'strip-markdown'; 3 | 4 | /** 5 | * Remove markdown characters from a raw markdown file. 6 | */ 7 | export const removeMarkdown = (markdown: string): string => { 8 | const body = markdown 9 | .match(/^---\n.*---\n(.*)/s)![1] 10 | .replace(/\n/g, ' ') 11 | .trim(); 12 | const { contents } = remark().use(stripMarkdown).processSync(body); 13 | 14 | return contents as string; 15 | }; 16 | -------------------------------------------------------------------------------- /config/plugins/capitalize.ts: -------------------------------------------------------------------------------- 1 | import { titleCase } from 'title-case'; 2 | import { Node } from 'unist'; 3 | import visit from 'unist-util-visit'; 4 | 5 | interface TextNode extends Node { 6 | value: string; 7 | } 8 | 9 | const capitalize = 10 | () => 11 | (tree: Node): void => { 12 | visit(tree, 'heading', (node) => { 13 | visit(node, 'text', (textNode) => { 14 | const text = textNode.value ?? ''; 15 | textNode.value = titleCase(text); 16 | }); 17 | }); 18 | }; 19 | 20 | export default capitalize; 21 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const { useGatsbyConfig } = require('gatsby-plugin-ts-config'); 2 | 3 | /** 4 | * Config files are stored in the `gatsby` folder. 5 | */ 6 | module.exports = useGatsbyConfig(() => require('./config/gatsby-config'), { 7 | type: 'babel' 8 | }); 9 | -------------------------------------------------------------------------------- /gatsby-node.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs'; 2 | import { join, resolve } from 'path'; 3 | import { 4 | CreatePageArgs, 5 | CreatePagesArgs, 6 | CreateResolversArgs, 7 | CreateSchemaCustomizationArgs, 8 | GatsbyNode, 9 | Node, 10 | NodeModel, 11 | Resolvers 12 | } from 'gatsby'; 13 | import { GatsbyIterable } from 'gatsby/dist/datastore/common/iterable'; 14 | import { titleCase } from 'title-case'; 15 | import { parse } from 'yaml'; 16 | import { POPULAR_ARTICLES } from './src/config'; 17 | import { Breadcrumb, MdxNode, YamlNode } from './src/types'; 18 | 19 | const REDIRECTS_FILE = resolve(__dirname, './content/redirects.yml'); 20 | 21 | type FileNode = Node & { 22 | relativePath: string; 23 | relativeDirectory: string; 24 | }; 25 | 26 | const gatsbyNode: GatsbyNode = { 27 | /** 28 | * Customizes the GraphQL schema with knowledge base-specific fields. 29 | */ 30 | async createSchemaCustomization({ actions }: CreateSchemaCustomizationArgs): Promise { 31 | const { createTypes, createFieldExtension } = actions; 32 | 33 | const typeDefs = ` 34 | type Breadcrumb { 35 | title: String! 36 | slug: String! 37 | } 38 | 39 | type Mdx implements Node { 40 | category: Yaml! 41 | slug: String! 42 | frontmatter: MdxFrontmatter! 43 | breadcrumbs: [Breadcrumb]! 44 | relatedArticles: [Mdx] 45 | } 46 | 47 | type MdxFrontmatter { 48 | title: String! @titleCase 49 | } 50 | 51 | type Yaml implements Node { 52 | title: String! @titleCase 53 | extendedDescription: Mdx 54 | slug: String! 55 | category: Yaml 56 | parentCategory: Yaml 57 | pages: [Mdx] 58 | popularArticles: [Mdx] 59 | totalArticles: Int! 60 | categories: [Yaml] 61 | breadcrumbs: [Breadcrumb]! 62 | } 63 | `; 64 | 65 | createTypes(typeDefs); 66 | 67 | createFieldExtension({ 68 | name: 'titleCase', 69 | extend: () => ({ 70 | resolve(source: Node, _: null, __: null, info: { fieldName: string }) { 71 | return titleCase(source[info.fieldName] as string); 72 | } 73 | }) 74 | }); 75 | }, 76 | 77 | /** 78 | * Adds resolvers for the added GraphQL fields. 79 | */ 80 | async createResolvers({ createResolvers }: CreateResolversArgs): Promise { 81 | const getPageSlug = (node: Node, nodeModel: NodeModel): string => { 82 | const { relativePath } = nodeModel.getNodeById({ id: node.parent! }); 83 | return relativePath.replace(/\.md$/, ''); 84 | }; 85 | 86 | const getCategorySlug = (node: Node, nodeModel: NodeModel): string => { 87 | const parent = nodeModel.getNodeById({ id: node.parent! }); 88 | return parent.relativePath.replace(/\/category\.yml$/, ''); 89 | }; 90 | 91 | const getBreadcrumbs = async (breadcrumbs: Breadcrumb[], nodeModel: NodeModel): Promise => { 92 | const parentSlug = join(breadcrumbs[0].slug, '..'); 93 | if (parentSlug === '.') { 94 | return breadcrumbs; 95 | } 96 | 97 | const parent = await nodeModel.findOne({ 98 | type: 'Yaml', 99 | query: { 100 | filter: { 101 | slug: { 102 | eq: parentSlug 103 | } 104 | } 105 | } 106 | }); 107 | 108 | if (parent) { 109 | const newBreadcrumbs = [ 110 | { 111 | title: titleCase(parent.title), 112 | slug: getCategorySlug(parent, nodeModel) 113 | }, 114 | ...breadcrumbs 115 | ]; 116 | 117 | return getBreadcrumbs(newBreadcrumbs, nodeModel); 118 | } 119 | 120 | return breadcrumbs; 121 | }; 122 | 123 | const resolvers: Resolvers = { 124 | Mdx: { 125 | slug: { 126 | resolve: (node: Node, _, { nodeModel }) => getPageSlug(node, nodeModel) 127 | }, 128 | 129 | category: { 130 | async resolve(node: Node, _, { nodeModel }): Promise { 131 | const { relativeDirectory } = nodeModel.getNodeById({ id: node.parent! }); 132 | 133 | return nodeModel.findOne({ 134 | type: 'Yaml', 135 | query: { 136 | filter: { 137 | slug: { 138 | eq: relativeDirectory 139 | } 140 | } 141 | } 142 | }); 143 | } 144 | }, 145 | 146 | breadcrumbs: { 147 | async resolve(node: Node, _, { nodeModel }): Promise { 148 | return getBreadcrumbs( 149 | [ 150 | { 151 | title: (node as MdxNode).frontmatter.title as string, 152 | slug: getPageSlug(node, nodeModel) 153 | } 154 | ], 155 | nodeModel 156 | ); 157 | } 158 | }, 159 | 160 | relatedArticles: { 161 | async resolve(node: Node, _, { nodeModel }): Promise | undefined> { 162 | const mdxNode = node as MdxNode; 163 | 164 | if (mdxNode.frontmatter.related_articles) { 165 | const { entries } = await nodeModel.findAll({ 166 | type: 'Mdx', 167 | query: { 168 | filter: { 169 | slug: { 170 | in: mdxNode.frontmatter.related_articles 171 | } 172 | } 173 | } 174 | }); 175 | 176 | return entries; 177 | } 178 | } 179 | } 180 | }, 181 | 182 | Yaml: { 183 | slug: { 184 | resolve: (node: Node, _, { nodeModel }) => getCategorySlug(node, nodeModel) 185 | }, 186 | 187 | extendedDescription: { 188 | async resolve(node: Node, _, { nodeModel }): Promise { 189 | const { relativeDirectory } = nodeModel.getNodeById({ id: node.parent! }); 190 | 191 | return nodeModel.findOne({ 192 | type: 'Mdx', 193 | query: { 194 | filter: { 195 | slug: { 196 | eq: `${relativeDirectory}/description` 197 | } 198 | } 199 | } 200 | }); 201 | } 202 | }, 203 | 204 | /** 205 | * Top level category. 206 | */ 207 | parentCategory: { 208 | async resolve(node: Node, _, { nodeModel }): Promise { 209 | const slug = getCategorySlug(node, nodeModel); 210 | const parentSlug = slug.split('/')[0]; 211 | 212 | if (slug === parentSlug) { 213 | return node; 214 | } 215 | 216 | return nodeModel.findOne({ 217 | type: 'Yaml', 218 | query: { 219 | filter: { 220 | slug: { 221 | eq: parentSlug 222 | } 223 | } 224 | } 225 | }); 226 | } 227 | }, 228 | 229 | /** 230 | * Direct parent category. 231 | */ 232 | category: { 233 | async resolve(node: Node, _, { nodeModel }): Promise { 234 | const { relativeDirectory } = nodeModel.getNodeById({ id: node.parent! }); 235 | const parentDirectory = join(relativeDirectory, '..'); 236 | 237 | if (parentDirectory === '.') { 238 | return; 239 | } 240 | 241 | return nodeModel.findOne({ 242 | type: 'Yaml', 243 | query: { 244 | filter: { 245 | slug: { 246 | eq: parentDirectory 247 | } 248 | } 249 | } 250 | }); 251 | } 252 | }, 253 | 254 | /** 255 | * Child categories. 256 | */ 257 | categories: { 258 | async resolve(node: Node, _, { nodeModel }): Promise | undefined> { 259 | if (!node.categories) { 260 | return; 261 | } 262 | 263 | const slug = getCategorySlug(node, nodeModel); 264 | const slugs = (node.categories as string[]).map((category) => `${slug}/${category}`); 265 | 266 | const { entries } = await nodeModel.findAll({ 267 | type: 'Yaml', 268 | query: { 269 | filter: { 270 | slug: { 271 | in: slugs 272 | } 273 | } 274 | } 275 | }); 276 | 277 | return entries; 278 | } 279 | }, 280 | 281 | /** 282 | * Child pages. 283 | */ 284 | pages: { 285 | async resolve(node: Node, _, { nodeModel }): Promise | undefined> { 286 | if (!node.articles) { 287 | return; 288 | } 289 | 290 | const slug = getCategorySlug(node, nodeModel); 291 | const slugs = (node.articles as string[]).map((page) => `${slug}/${page}`); 292 | 293 | const { entries } = await nodeModel.findAll({ 294 | type: 'Mdx', 295 | query: { 296 | filter: { 297 | slug: { 298 | in: slugs 299 | } 300 | } 301 | } 302 | }); 303 | 304 | return entries; 305 | } 306 | }, 307 | 308 | breadcrumbs: { 309 | async resolve(node: Node, _, { nodeModel }): Promise { 310 | return getBreadcrumbs( 311 | [ 312 | { 313 | title: (node as YamlNode).title as string, 314 | slug: getCategorySlug(node, nodeModel) 315 | } 316 | ], 317 | nodeModel 318 | ); 319 | } 320 | }, 321 | 322 | popularArticles: { 323 | async resolve(node: Node, _, { nodeModel }): Promise> { 324 | if (!node.popular_articles) { 325 | return new GatsbyIterable([]); 326 | } 327 | 328 | const slug = getCategorySlug(node, nodeModel); 329 | const slugs = (node.popular_articles as string[]).map((page) => `${slug}/${page}`); 330 | 331 | const { entries } = await nodeModel.findAll({ 332 | type: 'Mdx', 333 | query: { 334 | filter: { 335 | slug: { 336 | in: slugs 337 | } 338 | } 339 | } 340 | }); 341 | 342 | return entries; 343 | } 344 | }, 345 | 346 | totalArticles: { 347 | async resolve(node: Node, _, { nodeModel }): Promise { 348 | const slug = getCategorySlug(node, nodeModel); 349 | 350 | const { totalCount } = await nodeModel.findAll({ 351 | type: 'Mdx', 352 | query: { 353 | filter: { 354 | slug: { 355 | glob: `${slug}/**` 356 | } 357 | } 358 | } 359 | }); 360 | 361 | return totalCount(); 362 | } 363 | } 364 | } 365 | }; 366 | 367 | createResolvers(resolvers); 368 | }, 369 | 370 | async createPages({ actions: { createRedirect } }: CreatePagesArgs): Promise { 371 | const { redirects } = parse(await fs.readFile(REDIRECTS_FILE, 'utf-8')); 372 | 373 | redirects.forEach((redirect: { from: string; to: string }) => { 374 | createRedirect({ 375 | fromPath: `/${redirect.from}`, 376 | toPath: `/${redirect.to}`, 377 | isPermanent: true 378 | }); 379 | }); 380 | }, 381 | 382 | async onCreatePage({ 383 | page, 384 | actions: { deletePage, createPage } 385 | }: CreatePageArgs>): Promise { 386 | deletePage(page); 387 | 388 | const slug = page.context.slug as string | undefined; 389 | 390 | // Skip description files 391 | if (slug?.startsWith('troubleshooter') && slug?.endsWith('description')) { 392 | return; 393 | } 394 | 395 | // Override component for troubleshooter pages 396 | if (slug?.startsWith('troubleshooter')) { 397 | return createPage({ 398 | ...page, 399 | component: require.resolve('./src/components/Troubleshooter.tsx'), 400 | context: { 401 | ...page.context, 402 | popularArticles: POPULAR_ARTICLES, 403 | glob: `${page.context.slug}/**` 404 | } 405 | }); 406 | } 407 | 408 | createPage({ 409 | ...page, 410 | context: { 411 | ...page.context, 412 | popularArticles: POPULAR_ARTICLES, 413 | glob: `${page.context.slug}/**` 414 | } 415 | }); 416 | } 417 | }; 418 | 419 | export default gatsbyNode; 420 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "knowledge-base", 3 | "version": "3.1.3", 4 | "description": "MyCrypto Knowledge Base", 5 | "author": "MyCrypto", 6 | "repository": "https://github.com/MyCryptoHQ/knowledge-base.git", 7 | "license": "MIT", 8 | "dependencies": { 9 | "@ethersproject/abi": "^5.5.0", 10 | "@ethersproject/bignumber": "^5.5.0", 11 | "@fontsource/lato": "^4.5.0", 12 | "@fontsource/roboto-mono": "^4.5.0", 13 | "@lingui/react": "^3.12.1", 14 | "@loadable/component": "^5.15.0", 15 | "@mycrypto/ui": "^1.9.1", 16 | "bignumber.js": "^9.0.1", 17 | "isomorphic-unfetch": "^3.1.0", 18 | "lodash.merge": "^4.6.2", 19 | "make-plural": "^7.0.0", 20 | "query-string": "^7.0.1", 21 | "react": "^17.0.2", 22 | "react-dom": "^17.0.2", 23 | "react-helmet": "^6.1.0", 24 | "react-is": "^17.0.2", 25 | "regenerator-runtime": "^0.13.9", 26 | "styled-components": "^5.3.3" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.15.8", 30 | "@babel/plugin-transform-modules-commonjs": "^7.15.4", 31 | "@babel/preset-env": "^7.15.8", 32 | "@babel/preset-typescript": "^7.15.0", 33 | "@lingui/babel-preset-react": "^2.9.2", 34 | "@lingui/cli": "^3.12.1", 35 | "@lingui/macro": "^3.12.1", 36 | "@mdx-js/mdx": "^1.6.22", 37 | "@mdx-js/react": "^1.6.22", 38 | "@types/loadable__component": "^5.13.4", 39 | "@types/lodash.merge": "^4.6.6", 40 | "@types/node": "^16.11.5", 41 | "@types/react": "^17.0.32", 42 | "@types/react-copy-to-clipboard": "^5.0.2", 43 | "@types/react-dom": "^17.0.10", 44 | "@types/react-google-recaptcha": "^2.1.2", 45 | "@types/react-helmet": "^6.1.4", 46 | "@types/styled-components": "^5.1.15", 47 | "@typescript-eslint/eslint-plugin": "^5.2.0", 48 | "@typescript-eslint/parser": "^5.2.0", 49 | "babel-plugin-styled-components": "^1.13.3", 50 | "babel-preset-gatsby": "^2.0.0", 51 | "cross-env": "^7.0.3", 52 | "eslint": "^8.1.0", 53 | "eslint-config-prettier": "^8.3.0", 54 | "eslint-plugin-import": "^2.25.2", 55 | "eslint-plugin-react": "^7.26.1", 56 | "eslint-plugin-react-hooks": "^4.2.0", 57 | "gatsby": "^4.0.1", 58 | "gatsby-plugin-aws-elasticsearch": "^0.2.1", 59 | "gatsby-plugin-canonical-urls": "^4.0.0", 60 | "gatsby-plugin-catch-links": "^4.0.0", 61 | "gatsby-plugin-git-clone": "^0.1.0", 62 | "gatsby-plugin-manifest": "^4.0.0", 63 | "gatsby-plugin-matomo": "^0.10.0", 64 | "gatsby-plugin-mdx": "^3.0.0", 65 | "gatsby-plugin-react-helmet": "^5.0.0", 66 | "gatsby-plugin-s3": "^0.3.8", 67 | "gatsby-plugin-sharp": "^4.0.0", 68 | "gatsby-plugin-sitemap": "^5.0.0", 69 | "gatsby-plugin-styled-components": "^5.0.0", 70 | "gatsby-plugin-ts-config": "^2.1.3", 71 | "gatsby-plugin-typescript": "^4.0.0", 72 | "gatsby-remark-copy-linked-files": "^5.0.0", 73 | "gatsby-remark-external-links": "^0.0.4", 74 | "gatsby-remark-images": "^6.0.0", 75 | "gatsby-remark-static-images": "^1.2.1", 76 | "gatsby-source-filesystem": "^4.0.0", 77 | "gatsby-transformer-yaml": "^4.0.0", 78 | "husky": "^4.3.8", 79 | "lint-staged": "^11.2.4", 80 | "prettier": "^2.4.1", 81 | "rehype-slug": "^4.0.1", 82 | "remark": "^13.0.0", 83 | "remark-kbd": "^1.1.0", 84 | "slash": "^4.0.0", 85 | "strip-markdown": "^4.2.0", 86 | "title-case": "^3.0.3", 87 | "ts-node": "^10.4.0", 88 | "typescript": "^4.4.4", 89 | "unist-util-visit": "^2.0.3", 90 | "yaml": "^1.10.2" 91 | }, 92 | "scripts": { 93 | "clean": "gatsby clean", 94 | "start": "gatsby develop", 95 | "analyze": "cross-env ANALYZE_BUNDLE=true yarn build", 96 | "build": "gatsby build", 97 | "serve": "gatsby serve", 98 | "deploy": "gatsby-plugin-s3 deploy --yes", 99 | "extract": "lingui extract", 100 | "compile": "lingui compile", 101 | "lint": "yarn run lint:tsc && yarn run lint:eslint && yarn run lint:prettier", 102 | "lint:tsc": "tsc --noEmit", 103 | "lint:eslint": "eslint . --ignore-path .gitignore --ext .ts,.tsx,.js,.jsx", 104 | "lint:prettier": "prettier --check '**/*.{ts,tsx,js,json}' --ignore-path .gitignore", 105 | "prettier": "prettier --write '**/*.{ts,tsx,js,json}' --ignore-path .gitignore" 106 | }, 107 | "lint-staged": { 108 | "*.{ts,tsx}": [ 109 | "prettier --write", 110 | "eslint --fix" 111 | ], 112 | "*.{js,json}": [ 113 | "prettier --write" 114 | ] 115 | }, 116 | "husky": { 117 | "hooks": { 118 | "pre-commit": "lint-staged" 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Root.tsx: -------------------------------------------------------------------------------- 1 | import { i18n } from '@lingui/core'; 2 | import { I18nProvider } from '@lingui/react'; 3 | import { en as plurals } from 'make-plural/plurals'; 4 | import { FunctionComponent } from 'react'; 5 | import { ThemeProvider } from 'styled-components'; 6 | import { Layout } from './components'; 7 | import { messages as en } from './locales/en/messages'; 8 | import { theme } from './theme'; 9 | import 'regenerator-runtime'; 10 | 11 | i18n.loadLocaleData('en', { plurals }); 12 | i18n.load({ en }); 13 | i18n.activate('en'); 14 | 15 | const Root: FunctionComponent = ({ children }) => ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | 23 | export default Root; 24 | -------------------------------------------------------------------------------- /src/assets/fonts/MyIcons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/fonts/MyIcons.eot -------------------------------------------------------------------------------- /src/assets/fonts/MyIcons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by FontForge 20170731 at Fri Feb 23 02:31:18 2018 6 | By Aleksey,,, 7 | 8 | 9 | 10 | 23 | 25 | 27 | 29 | 31 | 34 | 38 | 41 | 45 | 47 | 50 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/assets/fonts/MyIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/fonts/MyIcons.ttf -------------------------------------------------------------------------------- /src/assets/fonts/MyIcons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/fonts/MyIcons.woff -------------------------------------------------------------------------------- /src/assets/fonts/MyIcons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/fonts/MyIcons.woff2 -------------------------------------------------------------------------------- /src/assets/images/banners/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/black.png -------------------------------------------------------------------------------- /src/assets/images/banners/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/blue.png -------------------------------------------------------------------------------- /src/assets/images/banners/dark-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/dark-blue.png -------------------------------------------------------------------------------- /src/assets/images/banners/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/green.png -------------------------------------------------------------------------------- /src/assets/images/banners/light-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/light-blue.png -------------------------------------------------------------------------------- /src/assets/images/banners/light-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/banners/light-green.png -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyCryptoHQ/knowledge-base/c1cc8b260221fd4a111046c285fcf7d328a535be/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/images/logos/coinbase.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/logos/ledger.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/logos/quicknode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/logos/trezor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/logos/unstoppable-domains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/images/membership.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/assets/images/sad-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 33 | 35 | 37 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/Article.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Body, Box, Button, Flex, InlineBody, SubHeading } from '@mycrypto/ui'; 3 | import { graphql } from 'gatsby'; 4 | import { FunctionComponent } from 'react'; 5 | import { Mdx } from '../types'; 6 | import Card from './Card'; 7 | import { Label } from './Label'; 8 | import { Link } from './Link'; 9 | 10 | export interface ArticleCardProps { 11 | article: Mdx; 12 | } 13 | 14 | export const Article: FunctionComponent = ({ article }) => { 15 | return ( 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | {article.frontmatter.title} 28 | 29 | {article.excerpt} 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export const query = graphql` 43 | fragment Article on Mdx { 44 | slug 45 | excerpt(pruneLength: 200) 46 | timeToRead 47 | category { 48 | parentCategory { 49 | title 50 | badge 51 | slug 52 | } 53 | } 54 | frontmatter { 55 | title 56 | } 57 | } 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/Articles.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps, SubHeading } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | import { Grid } from './Grid'; 4 | 5 | export interface ArticlesProps { 6 | title: string; 7 | columns?: number; 8 | } 9 | 10 | export const Articles: FunctionComponent = ({ title, columns = 3, children, ...props }) => ( 11 | 12 | 13 | {title} 14 | 15 | {children} 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /src/components/Banner/Banner.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, FlexProps } from '@mycrypto/ui'; 2 | import { FunctionComponent, ReactElement } from 'react'; 3 | import black from '../../assets/images/banners/black.png'; 4 | import blue from '../../assets/images/banners/blue.png'; 5 | import darkBlue from '../../assets/images/banners/dark-blue.png'; 6 | import green from '../../assets/images/banners/green.png'; 7 | import lightBlue from '../../assets/images/banners/light-blue.png'; 8 | import lightGreen from '../../assets/images/banners/light-green.png'; 9 | 10 | export interface BannerProps { 11 | type: string; 12 | left: ReactElement; 13 | } 14 | 15 | const BANNER_TYPES = { 16 | black, 17 | blue, 18 | darkBlue, 19 | green, 20 | lightBlue, 21 | lightGreen 22 | }; 23 | 24 | export const Banner: FunctionComponent = ({ type, left, children, ...props }) => ( 25 | 35 | 36 | {left} 37 | 38 | 47 | {children} 48 | 49 | 50 | ); 51 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/CoinbaseBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/logos/coinbase.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const CoinbaseBanner: FunctionComponent = (props) => ( 9 | 10 | 14 | {t`Coinbase`} 15 | 16 | } 17 | {...props}> 18 | 19 | 20 | Buy ETH today 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/LedgerBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/logos/ledger.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const LedgerBanner: FunctionComponent = (props) => ( 9 | 10 | 14 | {t`Ledger`} 15 | 16 | } 17 | {...props}> 18 | 19 | 20 | Get a Ledger hardware wallet 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/MembershipBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/membership.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const MembershipBanner: FunctionComponent = (props) => ( 9 | 12 | 16 | {t`Ledger`} 17 | 18 | } 19 | {...props}> 20 | 21 | 22 | Support MyCrypto, become a member! 23 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/QuickNodeBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/logos/quicknode.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const QuickNodeBanner: FunctionComponent = (props) => ( 9 | 10 | 14 | {t`QuickNode`} 15 | 16 | } 17 | {...props}> 18 | 19 | 20 | Get your own node today 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/TrezorBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/logos/trezor.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const TrezorBanner: FunctionComponent = (props) => ( 9 | 10 | 14 | {t`Trezor`} 15 | 16 | } 17 | {...props}> 18 | 19 | 20 | Get a Trezor hardware wallet 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/UnstoppableDomainsBanner.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Flex, FlexProps, Image } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import logo from '../../../assets/images/logos/unstoppable-domains.svg'; 5 | import { Link } from '../../Link'; 6 | import { Banner } from '../Banner'; 7 | 8 | export const UnstoppableDomainsBanner: FunctionComponent = (props) => ( 9 | 10 | 14 | {t`Unstoppable 15 | 16 | } 17 | {...props}> 18 | 19 | 20 | Buy a .crypto domain today 21 | 22 | 23 | 24 | 25 | ); 26 | -------------------------------------------------------------------------------- /src/components/Banner/Banners/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CoinbaseBanner'; 2 | export * from './LedgerBanner'; 3 | export * from './MembershipBanner'; 4 | export * from './QuickNodeBanner'; 5 | export * from './TrezorBanner'; 6 | export * from './UnstoppableDomainsBanner'; 7 | -------------------------------------------------------------------------------- /src/components/Banner/RandomBanner.tsx: -------------------------------------------------------------------------------- 1 | import loadable from '@loadable/component'; 2 | 3 | export const RandomBanner = loadable(() => import('./StaticRandomBanner')); 4 | -------------------------------------------------------------------------------- /src/components/Banner/StaticRandomBanner.tsx: -------------------------------------------------------------------------------- 1 | import { FlexProps } from '@mycrypto/ui'; 2 | import { FunctionComponent, useMemo } from 'react'; 3 | import { 4 | CoinbaseBanner, 5 | LedgerBanner, 6 | MembershipBanner, 7 | QuickNodeBanner, 8 | TrezorBanner, 9 | UnstoppableDomainsBanner 10 | } from './Banners'; 11 | 12 | const BANNERS = [ 13 | CoinbaseBanner, 14 | LedgerBanner, 15 | MembershipBanner, 16 | QuickNodeBanner, 17 | TrezorBanner, 18 | UnstoppableDomainsBanner 19 | ]; 20 | 21 | const StaticRandomBanner: FunctionComponent = (props) => { 22 | const Banner = useMemo(() => BANNERS[Math.floor(Math.random() * BANNERS.length)], []); 23 | 24 | return ; 25 | }; 26 | 27 | export default StaticRandomBanner; 28 | -------------------------------------------------------------------------------- /src/components/Banner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Banners'; 2 | export * from './Banner'; 3 | export * from './RandomBanner'; 4 | -------------------------------------------------------------------------------- /src/components/Card.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | export type CardProps = BoxProps; 5 | 6 | export const Card: FunctionComponent = ({ children, ...props }) => ( 7 | 12 | {children} 13 | 14 | ); 15 | 16 | export default Card; 17 | -------------------------------------------------------------------------------- /src/components/Categories.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps, IconType } from '@mycrypto/ui'; 2 | import { graphql, useStaticQuery } from 'gatsby'; 3 | import { FunctionComponent } from 'react'; 4 | import { Yaml } from '../types'; 5 | import { CategoryButton } from './CategoryButton'; 6 | import { Link } from './Link'; 7 | 8 | interface QueryData { 9 | allYaml: { 10 | nodes: Yaml[]; 11 | }; 12 | } 13 | 14 | export interface CategoriesProps { 15 | exclude?: string; 16 | } 17 | 18 | export const Categories: FunctionComponent = ({ exclude, ...props }) => { 19 | const { allYaml } = useStaticQuery(graphql` 20 | query { 21 | allYaml( 22 | filter: { category: { slug: { eq: null } }, slug: { ne: "troubleshooter" } } 23 | sort: { order: DESC, fields: [priority] } 24 | ) { 25 | nodes { 26 | title 27 | slug 28 | icon { 29 | small 30 | } 31 | } 32 | } 33 | } 34 | `); 35 | 36 | return ( 37 | 47 | {allYaml.nodes 48 | .filter(({ slug }) => slug !== exclude) 49 | .map(({ title, slug, icon }) => ( 50 | 51 | {title} 52 | 53 | ))} 54 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/components/Category.tsx: -------------------------------------------------------------------------------- 1 | import { Body, Box, Flex, Icon, SubHeading } from '@mycrypto/ui'; 2 | import { ElementType, FunctionComponent } from 'react'; 3 | import { Yaml } from '../types'; 4 | import { Grid } from './Grid'; 5 | import { Link } from './Link'; 6 | 7 | export interface CategoryProps { 8 | category: Yaml; 9 | depth?: number; 10 | } 11 | 12 | export const Category: FunctionComponent = ({ category, depth = 0 }) => ( 13 | 26 | {category.pages && category.pages.length > 0 && ( 27 | 28 | {category.pages.map((page) => ( 29 | 30 | 31 | 32 | {page.frontmatter.title} 33 | 34 | 35 | 36 | 37 | ))} 38 | 39 | )} 40 | 41 | {category.categories && category.categories.length > 0 && ( 42 | <> 43 | {category.categories.map((category) => ( 44 | 45 | 46 | {category.title} 47 | 48 | 49 | 50 | ))} 51 | 52 | )} 53 | 54 | ); 55 | -------------------------------------------------------------------------------- /src/components/CategoryButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Icon, IconType } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | export interface CategoryButtonProps { 5 | icon: IconType; 6 | } 7 | 8 | export const CategoryButton: FunctionComponent = ({ icon, children }) => ( 9 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/components/Divider.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | export type DividerProps = BoxProps; 5 | 6 | export const Divider: FunctionComponent = (props) => ( 7 | 8 | ); 9 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Footer as UIFooter, 3 | FooterAbout, 4 | FooterDonateAndSubscribe, 5 | FooterLink, 6 | FooterLinkColumn, 7 | FooterLinks, 8 | FooterSeparator 9 | } from '@mycrypto/ui'; 10 | import { FunctionComponent } from 'react'; 11 | import { FOOTER_LINK_COLUMNS } from '../config'; 12 | import { Link } from './Link'; 13 | 14 | export const Footer: FunctionComponent = () => ( 15 | 16 | 17 | 18 | 19 | {FOOTER_LINK_COLUMNS.map((column) => ( 20 | 21 | {column.links.map((link, index) => ( 22 | 23 | 24 | {link.title} 25 | 26 | 27 | ))} 28 | 29 | ))} 30 | 31 | 32 | 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /src/components/Grid.tsx: -------------------------------------------------------------------------------- 1 | import { Box, BoxProps } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | 4 | export interface GridProps { 5 | columns: number; 6 | } 7 | 8 | export const Grid: FunctionComponent = ({ columns, children, ...props }) => ( 9 | 16 | {children} 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { t } from '@lingui/macro'; 2 | import { Box, Header as UIHeader, HeaderButton, Logo } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import { Link } from './Link'; 5 | 6 | export const Header: FunctionComponent = ({ children }) => ( 7 | 15 | 16 | 17 | 18 | 19 | } 20 | centerComponents={ 21 | 22 | 23 | 24 | } 25 | rightComponents={ 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | }> 40 | {children} 41 | 42 | ); 43 | -------------------------------------------------------------------------------- /src/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import { t, Trans } from '@lingui/macro'; 2 | import { Body, Box, Heading } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import { Categories } from './Categories'; 5 | import { Search } from './Search'; 6 | 7 | export const Hero: FunctionComponent = () => ( 8 | 9 | 10 | What can we help you with? 11 | 12 | 13 | Search or browse for articles to help you get started or get un-stuck. 14 | 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from '@mycrypto/ui'; 2 | import { FunctionComponent } from 'react'; 3 | import { Yaml } from '../types'; 4 | import { Link } from './Link'; 5 | 6 | export interface LabelProps { 7 | category: Yaml; 8 | } 9 | 10 | export const Label: FunctionComponent = ({ category }) => ( 11 | 12 | 13 | {category.parentCategory.title} 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Trans } from '@lingui/macro'; 2 | import { Body, Flex, SubHeader } from '@mycrypto/ui'; 3 | import { FunctionComponent } from 'react'; 4 | import { createGlobalStyle } from 'styled-components'; 5 | import '@fontsource/lato/300.css'; 6 | import '@fontsource/lato/400.css'; 7 | import '@fontsource/lato/700.css'; 8 | import '@fontsource/roboto-mono/400.css'; 9 | import { Footer } from './Footer'; 10 | import { Header } from './Header'; 11 | import { Search } from './Search'; 12 | 13 | const GlobalStyle = createGlobalStyle` 14 | html, body, #___gatsby { 15 | margin: 0; 16 | height: 100%; 17 | } 18 | 19 | body { 20 | font-family: ${({ theme }) => theme.fonts.body}; 21 | background: ${({ theme }) => theme.colors.background.page}; 22 | } 23 | `; 24 | 25 | export const Layout: FunctionComponent = ({ children }) => ( 26 | 27 | 28 |
29 | 30 | 31 | 32 | What can we help you with? 33 | 34 | 35 | 36 | 37 |
38 | 39 | {children} 40 | 41 |