├── .editorconfig ├── .github └── workflows │ ├── greetings.yml │ └── npm-deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.ts └── lib │ ├── useArticles.ts │ ├── useFollowSuggestions.ts │ ├── useTags.ts │ └── useUser.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | root = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: 'Thank you for submitting your first issue to react-devto!' 13 | pr-message: 'Thank you for submitting your first pull request to react-devto!' 14 | -------------------------------------------------------------------------------- /.github/workflows/npm-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to NPM 2 | 3 | on: 4 | push: 5 | branches: 6 | - releases/* 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: '11.x' 16 | - name: Setup NPM 17 | run: echo '//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}'>.npmrc 18 | - name: Install NPM packages 19 | run: npm install 20 | - name: build 21 | run: npm run build 22 | - name: publish 23 | run: npm publish 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | dist 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-present Dominik Biedebach 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@d2k/react-devto` 2 | 3 | > React hooks for Dev.to integrations 4 | 5 | > You'll need to install `react`, `react-dom`, etc at `^16.8.4` 6 | 7 | * [Demo](https://bdbch.github.io/react-devto/) 8 | 9 | ## Install 10 | 11 | ```sh 12 | npm i @d2k/react-devto --save 13 | ``` 14 | 15 | # Usage 16 | 17 | ```js 18 | import { 19 | useArticles, 20 | useFollowSuggestions, 21 | useTags, 22 | useUser 23 | } from "@d2k/react-devto"; 24 | 25 | const MyComponent = () => { 26 | // useArticles(page, tag, username) 27 | const { articles, loading, error } = useArticles(); 28 | 29 | // useFollowSuggestions() 30 | const { suggestions, loading, error } = useFollowSuggestions(); 31 | 32 | // useTags(page) 33 | const { tags, loading, error } = useTags(); 34 | 35 | // useUser(username, id) 36 | const { user, loading, error } = useUser("bdbch"); 37 | }; 38 | ``` 39 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@d2k/react-devto", 3 | "version": "1.0.8", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/prop-types": { 8 | "version": "15.7.1", 9 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", 10 | "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", 11 | "dev": true 12 | }, 13 | "@types/react": { 14 | "version": "16.8.23", 15 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.8.23.tgz", 16 | "integrity": "sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA==", 17 | "dev": true, 18 | "requires": { 19 | "@types/prop-types": "*", 20 | "csstype": "^2.2.0" 21 | } 22 | }, 23 | "csstype": { 24 | "version": "2.6.6", 25 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", 26 | "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", 27 | "dev": true 28 | }, 29 | "js-tokens": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 32 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 33 | "dev": true 34 | }, 35 | "loose-envify": { 36 | "version": "1.4.0", 37 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 38 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 39 | "dev": true, 40 | "requires": { 41 | "js-tokens": "^3.0.0 || ^4.0.0" 42 | } 43 | }, 44 | "object-assign": { 45 | "version": "4.1.1", 46 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 47 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 48 | "dev": true 49 | }, 50 | "prop-types": { 51 | "version": "15.7.2", 52 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 53 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 54 | "dev": true, 55 | "requires": { 56 | "loose-envify": "^1.4.0", 57 | "object-assign": "^4.1.1", 58 | "react-is": "^16.8.1" 59 | } 60 | }, 61 | "react": { 62 | "version": "16.8.6", 63 | "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", 64 | "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", 65 | "dev": true, 66 | "requires": { 67 | "loose-envify": "^1.1.0", 68 | "object-assign": "^4.1.1", 69 | "prop-types": "^15.6.2", 70 | "scheduler": "^0.13.6" 71 | } 72 | }, 73 | "react-dom": { 74 | "version": "16.8.6", 75 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.6.tgz", 76 | "integrity": "sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA==", 77 | "dev": true, 78 | "requires": { 79 | "loose-envify": "^1.1.0", 80 | "object-assign": "^4.1.1", 81 | "prop-types": "^15.6.2", 82 | "scheduler": "^0.13.6" 83 | } 84 | }, 85 | "react-is": { 86 | "version": "16.8.6", 87 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", 88 | "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", 89 | "dev": true 90 | }, 91 | "scheduler": { 92 | "version": "0.13.6", 93 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz", 94 | "integrity": "sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ==", 95 | "dev": true, 96 | "requires": { 97 | "loose-envify": "^1.1.0", 98 | "object-assign": "^4.1.1" 99 | } 100 | }, 101 | "typescript": { 102 | "version": "3.5.3", 103 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", 104 | "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", 105 | "dev": true 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bdbch/react-devto", 3 | "version": "1.0.8", 4 | "description": "Dev.to hooks for Github API", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "repository": "https://github.com/bdbch/react-devto", 8 | "author": "@d2k", 9 | "license": "MIT", 10 | "publishConfig": { 11 | "registry": "https://npm.pkg.github.com/", 12 | "access": "public" 13 | }, 14 | "keywords": [ 15 | "dev.to", 16 | "hooks", 17 | "github" 18 | ], 19 | "files": [ 20 | "dist/**/*.*" 21 | ], 22 | "scripts": { 23 | "prepublish": "npm run build", 24 | "start": "tsc --watch", 25 | "build": "npm run clean && tsc", 26 | "clean": "rm -rf ./dist" 27 | }, 28 | "devDependencies": { 29 | "@types/react": "^16.8.23", 30 | "react": "^16.8.6", 31 | "react-dom": "^16.8.6", 32 | "typescript": "^3.5.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import useArticles from "./lib/useArticles"; 2 | import useFollowSuggestions from "./lib/useFollowSuggestions"; 3 | import useTags from "./lib/useTags"; 4 | import useUser from "./lib/useUser"; 5 | 6 | export interface IBaseUser { 7 | name: string; 8 | username: string; 9 | twitter_username?: string; 10 | github_username?: string; 11 | website_url?: string; 12 | profile_image?: string; 13 | profile_image_90?: string; 14 | } 15 | 16 | export interface IUser extends IBaseUser { 17 | type_of: "user"; 18 | summary?: string; 19 | location?: string; 20 | joined_at?: string; 21 | following?: boolean; 22 | } 23 | 24 | export interface ITag { 25 | id?: number; 26 | name: string; 27 | bg_color_hex: string; 28 | text_color_hex: string; 29 | } 30 | 31 | export interface IPost { 32 | type_of: "article"; 33 | id: number; 34 | title: string; 35 | description?: string; 36 | cover_image?: string; 37 | published_at: string; 38 | tag_list?: string[]; 39 | slug: string; 40 | path: string; 41 | url: string; 42 | canonical_url: string; 43 | comments_count: number; 44 | positive_reactions_count: number; 45 | published_timestamp: string; 46 | user: IBaseUser; 47 | flare_tag?: ITag; 48 | } 49 | 50 | export interface IResponse { 51 | loading: boolean; 52 | error?: string; 53 | } 54 | 55 | export interface IArticlesResponse extends IResponse { 56 | articles?: IPost[]; 57 | } 58 | 59 | export interface IUserResponse extends IResponse { 60 | user?: IUser; 61 | } 62 | 63 | export interface IFollowSuggestionsResponse extends IResponse { 64 | suggestions?: IUser[]; 65 | } 66 | 67 | export interface ITagsResponse extends IResponse { 68 | tags?: ITag[]; 69 | } 70 | 71 | export { useArticles, useUser, useFollowSuggestions, useTags }; 72 | 73 | export default { 74 | useArticles, 75 | useUser, 76 | useFollowSuggestions, 77 | useTags 78 | }; 79 | -------------------------------------------------------------------------------- /src/lib/useArticles.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { IArticlesResponse, IPost } from ".."; 4 | 5 | /** 6 | * Returns a list of articles 7 | * @param page filter articles by pagination 8 | * @param tag filter articles by tag 9 | * @param username receive articles by username 10 | */ 11 | export default function useArticles( 12 | page?: string, 13 | tag?: string, 14 | username?: string 15 | ): IArticlesResponse { 16 | const [articles, setArticles] = useState([]); 17 | const [loading, setLoading] = useState(false); 18 | const [error, setError] = useState(); 19 | 20 | useEffect(() => { 21 | let apiUrl = "https://dev.to/api/articles?"; 22 | const params = []; 23 | 24 | if (page) params.push(`page=${page}`); 25 | if (tag) params.push(`tag=${tag}`); 26 | if (username) params.push(`username=${username}`); 27 | apiUrl += params.join("&"); 28 | 29 | setLoading(true); 30 | setError(undefined); 31 | fetch(apiUrl) 32 | .then(res => res.json()) 33 | .then(data => { 34 | setLoading(false); 35 | setArticles(data); 36 | }) 37 | .catch(error => { 38 | setLoading(false); 39 | setError(error); 40 | }); 41 | }, [page, tag, username]); 42 | 43 | return { 44 | articles, 45 | loading, 46 | error 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/lib/useFollowSuggestions.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { IFollowSuggestionsResponse, IUser } from ".."; 4 | 5 | /** 6 | * returns follow suggestions for new users 7 | */ 8 | export default function useFollowSuggestions(): IFollowSuggestionsResponse { 9 | const [suggestions, setSuggestions] = useState([]); 10 | const [loading, setLoading] = useState(false); 11 | const [error, setError] = useState(); 12 | 13 | useEffect(() => { 14 | let apiUrl = "https://dev.to/api/users/?state=follow_suggestions"; 15 | 16 | setLoading(true); 17 | setError(undefined); 18 | fetch(apiUrl) 19 | .then(res => res.json()) 20 | .then(data => { 21 | setLoading(false); 22 | setSuggestions(data); 23 | }) 24 | .catch(error => { 25 | setLoading(false); 26 | setError(error); 27 | }); 28 | }, []); 29 | 30 | return { 31 | suggestions, 32 | loading, 33 | error 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/useTags.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | import { ITag, ITagsResponse } from ".."; 4 | 5 | /** 6 | * returns a list of tags 7 | * @param page filter by pagination 8 | */ 9 | export default function useTags(page?: number): ITagsResponse { 10 | const [tags, setTags] = useState([]); 11 | const [loading, setLoading] = useState(false); 12 | const [error, setError] = useState(); 13 | 14 | useEffect(() => { 15 | let apiUrl = "https://dev.to/api/tags?"; 16 | 17 | if (page) apiUrl += `page=${page}`; 18 | 19 | setLoading(true); 20 | setError(undefined); 21 | fetch(apiUrl) 22 | .then(res => res.json()) 23 | .then(data => { 24 | setLoading(false); 25 | setTags(data); 26 | }) 27 | .catch(error => { 28 | setLoading(false); 29 | setError(error); 30 | }); 31 | }, [page]); 32 | 33 | return { 34 | tags, 35 | loading, 36 | error 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/useUser.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from "react"; 2 | 3 | import { IUser, IUserResponse } from ".."; 4 | 5 | /** 6 | * returns a dev.to user by either the username or id 7 | * @param username the users username 8 | * @param id the users id 9 | */ 10 | export default function useUser(username?: string, id?: string): IUserResponse { 11 | const [user, setUser] = useState(); 12 | const [loading, setLoading] = useState(false); 13 | const [error, setError] = useState(); 14 | 15 | useCallback(() => { 16 | if ((username && username.length > 0) || (id && id.length > 0)) { 17 | const apiUrl = 18 | id && id.length > 0 19 | ? `https://dev.to/api/users/${id}` 20 | : `https://dev.to/api/users/by_username?url=${username}`; 21 | 22 | setError(undefined); 23 | setLoading(true); 24 | fetch(apiUrl) 25 | .then(res => res.json()) 26 | .then(data => { 27 | setLoading(false); 28 | setUser(data); 29 | }) 30 | .catch(err => { 31 | setLoading(false); 32 | setError(err); 33 | }); 34 | } 35 | }, [user, loading, error]); 36 | 37 | return { 38 | user, 39 | loading, 40 | error 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", 6 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 7 | "module": "commonjs", 8 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 9 | "lib": ["dom", "es2015"], 10 | /* Specify library files to be included in the compilation. */ 11 | "allowJs": false, 12 | /* Allow javascript files to be compiled. */ 13 | "checkJs": false, 14 | /* Report errors in .js files. */ 15 | "jsx": "react", 16 | /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 17 | "declaration": true, 18 | /* Generates corresponding '.d.ts' file. */ 19 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 20 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 21 | // "outFile": "./", /* Concatenate and emit output to single file. */ 22 | "outDir": "./dist", 23 | /* Redirect output structure to the directory. */ 24 | "rootDir": "./src", 25 | /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 26 | // "composite": true, /* Enable project compilation */ 27 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 28 | // "removeComments": true, /* Do not emit comments to output. */ 29 | // "noEmit": true, /* Do not emit outputs. */ 30 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 31 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 32 | "isolatedModules": false, 33 | /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 34 | 35 | /* Strict Type-Checking Options */ 36 | "strict": true, 37 | /* Enable all strict type-checking options. */ 38 | "noImplicitAny": true, 39 | /* Raise error on expressions and declarations with an implied 'any' type. */ 40 | "strictNullChecks": true, 41 | /* Enable strict null checks. */ 42 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 43 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 44 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 45 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 46 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 47 | 48 | /* Additional Checks */ 49 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 50 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 51 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 52 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 53 | 54 | /* Module Resolution Options */ 55 | "moduleResolution": "node", 56 | /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 57 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 58 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 59 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 60 | // "typeRoots": [], /* List of folders to include type definitions from. */ 61 | // "types": [], /* Type declaration files to be included in compilation. */ 62 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 63 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 64 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 65 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 66 | 67 | /* Source Map Options */ 68 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 69 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 70 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 71 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 72 | 73 | /* Experimental Options */ 74 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 75 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 76 | } 77 | } 78 | --------------------------------------------------------------------------------