6 |
7 | const success = response => {
8 | console.log(response) // eslint-disable-line
9 | }
10 |
11 | const error = response => {
12 | console.error(response) // eslint-disable-line
13 | }
14 |
15 | const loading = () => {
16 | console.log('loading') // eslint-disable-line
17 | }
18 |
19 | const App = () => (
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | (
37 |
40 | )}
41 | theme="dark"
42 | />
43 |
44 | )
45 |
46 | export default App
47 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import App from './app'
4 |
5 | const renderApp = Component => {
6 | const app = document.getElementById('google-contacts')
7 |
8 | render(, app)
9 | }
10 |
11 | renderApp(App)
12 |
13 | if (module.hot) {
14 | module.hot.accept('./app.js', () => {
15 | /* eslint-disable global-require */
16 | const app = require('./app').default
17 | renderApp(app)
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/dist/google-contacts.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.GoogleContacts=t(require("react")):e.GoogleContacts=t(e.react)}("undefined"!=typeof self?self:this,(function(e){return(()=>{"use strict";var t={437:(e,t,n)=>{n.r(t),n.d(t,{default:()=>O});var o=n(297),r=n.n(o);function i(e){var t=e.active;return r().createElement("div",{style:{background:t?"#eee":"#fff",borderRadius:2,marginRight:10,padding:10}},r().createElement("svg",{height:"18",width:"18",xmlns:"http://www.w3.org/2000/svg"},r().createElement("g",{fill:"#000",fillRule:"evenodd"},r().createElement("path",{d:"M9 3.48c1.69 0 2.83.73 3.48 1.34l2.54-2.48C13.46.89 11.43 0 9 0 5.48 0 2.44 2.02.96 4.96l2.91 2.26C4.6 5.05 6.62 3.48 9 3.48z",fill:"#EA4335"}),r().createElement("path",{d:"M17.64 9.2c0-.74-.06-1.28-.19-1.84H9v3.34h4.96c-.1.83-.64 2.08-1.84 2.92l2.84 2.2c1.7-1.57 2.68-3.88 2.68-6.62z",fill:"#4285F4"}),r().createElement("path",{d:"M3.88 10.78A5.54 5.54 0 0 1 3.58 9c0-.62.11-1.22.29-1.78L.96 4.96A9.008 9.008 0 0 0 0 9c0 1.45.35 2.82.96 4.04l2.92-2.26z",fill:"#FBBC05"}),r().createElement("path",{d:"M9 18c2.43 0 4.47-.8 5.96-2.18l-2.84-2.2c-.76.53-1.78.9-3.12.9-2.38 0-4.4-1.57-5.12-3.74L.97 13.04C2.45 15.98 5.48 18 9 18z",fill:"#34A853"}),r().createElement("path",{d:"M0 0h18v18H0z",fill:"none"}))))}const a=function(e){var t=e.children,n=e.icon;return r().createElement("span",{style:{paddingRight:10,fontWeight:500,paddingLeft:n?0:10,paddingTop:10,paddingBottom:10}},t)};function c(e){return c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},c(e)}function l(e){return function(e){if(Array.isArray(e))return s(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return s(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:null,o=this.props,r=o.onFailure,i=o.maxResults;e&&window.gapi.client.request({path:"https://people.googleapis.com/v1/people/me/connections",params:p({personFields:"names,emailAddresses",pageSize:i>1e3?1e3:i},n&&{pageToken:n})}).then((function(n){return t.handleNextDataFetch(n,e)}),(function(e){return r(e)}))}},{key:"handleNextDataFetch",value:function(e,t){var n=this.props.maxResults,o=JSON.parse(e.body);this.allData=[].concat(l(this.allData),l(o.connections)),"nextPageToken"in o&&n>this.allData.length?this.handleImportContacts(t,o.nextPageToken):this.handleParseContacts()}},{key:"handleParseContacts",value:function(){var e=this.props,t=e.onSuccess,n=e.onFailure,o=[];try{for(var r=0;r0&&o.push({email:i.emailAddresses[0].value,title:"names"in i?i.names[0].displayName:i.emailAddresses[0].value})}t(o)}catch(e){n("Error to fetch contacts")}}},{key:"signIn",value:function(e){var t=this;this.allData=[];var n=this.state.disable,o=this.props,r=o.prompt,i=o.onRequest,a=o.accessType,c=o.onSuccess;i(),e&&e.preventDefault(),n||("online"===a&&(this.client.callback=function(e){t.handleImportContacts(e)},null===window.gapi.client.getToken()?this.client.requestAccessToken({prompt:r}):this.client.requestAccessToken({prompt:""})),"offline"===a&&(this.client.callback=c,null===window.gapi.client.getToken()?this.client.requestCode({prompt:r}):this.client.requestCode({prompt:""})))}},{key:"render",value:function(){var e=this,t=this.props,n=t.tag,o=t.type,c=t.className,l=t.disabledStyle,s=t.buttonText,u=t.children,p=t.render,d=t.theme,f=t.icon,h=t.disabled,y=this.state,m=y.active,b=y.hovered,g=y.disabled||h;if(p)return p({onClick:this.signIn});var v={backgroundColor:"dark"===d?"rgb(66, 133, 244)":"#fff",display:"inline-flex",alignItems:"center",color:"dark"===d?"#fff":"rgba(0, 0, 0, .54)",boxShadow:"0 2px 2px 0 rgba(0, 0, 0, .24), 0 0 1px 0 rgba(0, 0, 0, .24)",padding:0,borderRadius:2,border:"1px solid transparent",fontSize:14,fontWeight:"500",fontFamily:"Roboto, sans-serif"},w={cursor:"pointer",opacity:.9},O={cursor:"pointer",backgroundColor:"dark"===d?"#3367D6":"#eee",color:"dark"===d?"#fff":"rgba(0, 0, 0, .54)",opacity:1},j=g?Object.assign({},v,l):m?Object.assign({},v,O):b?Object.assign({},v,w):v;return r().createElement(n,{onMouseEnter:function(){return e.setState({hovered:!0})},onMouseLeave:function(){return e.setState({hovered:!1,active:!1})},onMouseDown:function(){return e.setState({active:!0})},onMouseUp:function(){return e.setState({active:!1})},onClick:this.signIn,style:j,type:o,disabled:g,className:c},[f&&r().createElement(i,{key:1,active:m}),r().createElement(a,{key:2,icon:f},u||s)])}}],n&&f(t.prototype,n),o&&f(t,o),Object.defineProperty(t,"prototype",{writable:!1}),s}(o.Component);w.defaultProps={accessType:"online",buttonText:"Import from Gmail",disabled:!1,disabledStyle:{opacity:.6},icon:!0,maxResults:999,onRequest:function(){},prompt:"consent",tag:"button",theme:"light",type:"button",uxMode:"popup"};const O=w},297:t=>{t.exports=e}},n={};function o(e){if(n[e])return n[e].exports;var r=n[e]={exports:{}};return t[e](r,r.exports,o),r.exports}return o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o(437)})()}));
--------------------------------------------------------------------------------
/dist/google-contacts.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * The buffer module from node.js, for the browser.
3 | *
4 | * @author Feross Aboukhadijeh
5 | * @license MIT
6 | */
7 |
8 | /*! http://mths.be/fromcodepoint v0.1.0 by @mathias */
9 |
10 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
11 |
--------------------------------------------------------------------------------
/doc/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kwent/react-google-contacts/03ae01b891603d22d805a217a272b4687e549c95/doc/screenshot.png
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for react-google-contacts v0.0.1
2 | // Project: https://github.com/kwent/react-google-contacts
3 | // Definitions by: Ruslan Ibragimov
4 | import {Component, ReactNode, CSSProperties} from 'react';
5 |
6 | export as namespace ReactGoogleContacts;
7 |
8 | interface AuthResponse {
9 | readonly access_token: string;
10 | readonly id_token: string;
11 | readonly login_hint: string;
12 | readonly expires_in: number;
13 | readonly first_issued_at: number;
14 | readonly expires_at: number;
15 | }
16 |
17 | interface BasicProfile {
18 | getId(): string;
19 | getEmail(): string;
20 | getName(): string;
21 | getGivenName(): string;
22 | getFamilyName(): string;
23 | getImageUrl(): string;
24 | }
25 |
26 | // Based on https://developers.google.com/identity/sign-in/web/reference
27 | export interface GoogleContactsResponse {
28 | getBasicProfile(): BasicProfile;
29 | getAuthResponse(): AuthResponse;
30 | getGrantedScopes(): string;
31 | getHostedDomain(): string;
32 | getId(): string;
33 | hasGrantedScopes(scopes: string): boolean;
34 | disconnect(): void;
35 | grantOfflineAccess(options: GrantOfflineAccessOptions): Promise;
36 | signIn(options: SignInOptions): Promise;
37 | grant(options: SignInOptions): Promise;
38 | }
39 |
40 | interface GrantOfflineAccessOptions {
41 | readonly redirect_uri?: string;
42 | }
43 |
44 | interface SignInOptions {
45 | readonly app_package_name?: string;
46 | readonly fetch_basic_profile?: boolean;
47 | readonly prompt?: string;
48 | }
49 |
50 | export interface GoogleContactsResponseOffline {
51 | readonly code: string;
52 | }
53 |
54 | export interface GoogleContactsProps {
55 | readonly onSuccess: (response: GoogleContactsResponse | GoogleContactsResponseOffline) => void,
56 | readonly onFailure: (error: any) => void,
57 | readonly clientId: string,
58 | readonly jsSrc?: string,
59 | readonly onRequest?: () => void,
60 | readonly buttonText?: string,
61 | readonly className?: string,
62 | readonly redirectUri?: string,
63 | readonly cookiePolicy?: string,
64 | readonly loginHint?: string,
65 | readonly hostedDomain?: string,
66 | readonly prompt?: string,
67 | readonly responseType?: string,
68 | readonly children?: ReactNode,
69 | readonly style?: CSSProperties,
70 | readonly tag?: string,
71 | readonly disabled?: boolean;
72 | readonly uxMode?: string;
73 | readonly disabledStyle?: CSSProperties;
74 | readonly type?: string;
75 | readonly accessType?: string;
76 | readonly maxResults?: number;
77 | readonly render?: (props?: { onClick: () => void }) => JSX.Element;
78 | }
79 |
80 | export class GoogleContacts extends Component {
81 | public signIn(e?: Event): void;
82 | }
83 |
84 | export default GoogleContacts;
85 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-google-contacts",
3 | "version": "2.0.0",
4 | "description": "A Google Button to import user's gmail contacts",
5 | "main": "dist/google-contacts.js",
6 | "scripts": {
7 | "start": "webpack serve --config webpack/webpack.config.dev.js",
8 | "dev": "npm run start",
9 | "bundle": "webpack --config webpack/webpack.config.prod.js; git add ./dist/google-contacts.js",
10 | "test": "jest",
11 | "test:watch": "jest --watch",
12 | "lint": "eslint ./src",
13 | "lint:fix": "eslint ./src --fix",
14 | "clean": "rm -rf node_modules dist; rm yarn.lock"
15 | },
16 | "pre-commit": [
17 | "test",
18 | "lint",
19 | "bundle"
20 | ],
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/kwent/react-google-contacts.git"
24 | },
25 | "keywords": [
26 | "react",
27 | "reactjs",
28 | "react-component",
29 | "google-contacts",
30 | "google-oAuth2",
31 | "google-oAuth"
32 | ],
33 | "author": {
34 | "name": "Quentin Rousseau",
35 | "email": "contact@quent.in"
36 | },
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/kwent/react-google-contacts/issues"
40 | },
41 | "homepage": "https://github.com/kwent/react-google-contacts",
42 | "dependencies": {
43 | "@types/react": "*",
44 | "prop-types": "^15.6.0"
45 | },
46 | "devDependencies": {
47 | "@babel/core": "7.12.10",
48 | "@babel/plugin-proposal-object-rest-spread": "7.12.1",
49 | "@babel/plugin-transform-runtime": "7.12.10",
50 | "@babel/preset-env": "7.12.11",
51 | "@babel/preset-react": "7.12.10",
52 | "autoprefixer": "10.2.3",
53 | "babel-core": "7.0.0-bridge.0",
54 | "babel-jest": "26.6.3",
55 | "babel-loader": "8.2.2",
56 | "babel-plugin-transform-react-constant-elements": "6.23.0",
57 | "babel-plugin-transform-react-inline-elements": "6.22.0",
58 | "babel-plugin-transform-react-remove-prop-types": "0.4.24",
59 | "enzyme": "3.11.0",
60 | "enzyme-adapter-react-16": "1.15.6",
61 | "eslint-config-ag": "3.1.0",
62 | "jest": "26.6.3",
63 | "pre-commit": "^1.2.2",
64 | "react": "^17.0.1",
65 | "react-dom": "^17.0.1",
66 | "react-hot-loader": "4.13.0",
67 | "react-test-renderer": "17.0.1",
68 | "webpack": "5.17.0",
69 | "webpack-cli": "4.10.0",
70 | "webpack-dev-server": "3.11.2"
71 | },
72 | "peerDependencies": {
73 | "react": "^16.0.0",
74 | "react-dom": "^16.0.0"
75 | },
76 | "types": "./index.d.ts",
77 | "babel": {
78 | "presets": [
79 | "@babel/preset-env",
80 | "@babel/preset-react"
81 | ]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/button-content.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const ButtonContent = ({ children, icon }) => (
4 | {children}
5 | )
6 |
7 | export default ButtonContent
8 |
--------------------------------------------------------------------------------
/src/google-contacts.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-underscore-dangle */
2 | /* eslint-disable better-mutation/no-mutating-methods */
3 |
4 | import React, { Component } from 'react'
5 | import PropTypes from 'prop-types'
6 |
7 | import Icon from './icon'
8 | import ButtonContent from './button-content'
9 |
10 | const SCOPE = 'https://www.googleapis.com/auth/contacts.readonly'
11 |
12 | class GoogleContacts extends Component {
13 | constructor(props) {
14 | super(props)
15 | this.signIn = this.signIn.bind(this)
16 | this.handleImportContacts = this.handleImportContacts.bind(this)
17 | this.handleParseContacts = this.handleParseContacts.bind(this)
18 | this.loadApi = this.loadApi.bind(this)
19 | this.loadClient = this.loadClient.bind(this)
20 | this.state = {
21 | hovered: false,
22 | active: false
23 | }
24 | this.allData = []
25 | this.client = null
26 | }
27 |
28 | componentDidMount() {
29 | const element = document.getElementsByTagName('script')[0]
30 | const firstJs = element
31 |
32 | if (!document.getElementById('google-contacts-api')) {
33 | const js = document.createElement('script')
34 | js.id = 'google-contacts-api'
35 | js.src = 'https://apis.google.com/js/api.js'
36 | if (firstJs && firstJs.parentNode) {
37 | firstJs.parentNode.insertBefore(js, firstJs)
38 | } else {
39 | document.head.appendChild(js)
40 | }
41 | js.onload = this.loadApi
42 | }
43 |
44 | if (!document.getElementById('google-contacts-gsi')) {
45 | const js = document.createElement('script')
46 | js.id = 'google-contacts-gsi'
47 | js.src = 'https://accounts.google.com/gsi/client'
48 | if (firstJs && firstJs.parentNode) {
49 | firstJs.parentNode.insertBefore(js, firstJs)
50 | } else {
51 | document.head.appendChild(js)
52 | }
53 | js.onload = this.loadClient
54 | }
55 | }
56 |
57 | loadApi() {
58 | const { apiKey } = this.props
59 |
60 | window.gapi.load('client', () => {
61 | window.gapi.client.init({
62 | apiKey
63 | })
64 | })
65 | }
66 |
67 | loadClient() {
68 | const { clientId, hostedDomain, loginHint, accessType, onFailure, redirectUri, uxMode } = this.props
69 |
70 | if (accessType === 'online') {
71 | this.client = window.google.accounts.oauth2.initTokenClient({
72 | client_id: clientId,
73 | scope: SCOPE,
74 | hosted_domain: hostedDomain,
75 | hint: loginHint
76 | })
77 | }
78 |
79 | if (accessType === 'offline') {
80 | this.client = window.google.accounts.oauth2.initCodeClient({
81 | client_id: clientId,
82 | scope: SCOPE,
83 | hosted_domain: hostedDomain,
84 | hint: loginHint,
85 | redirect_uri: redirectUri,
86 | ux_mode: uxMode
87 | })
88 | }
89 |
90 | this.client.error_callback = onFailure
91 | }
92 |
93 | handleImportContacts(tokenResponse, pageToken = null) {
94 | const { onFailure, maxResults } = this.props
95 |
96 | if (tokenResponse) {
97 | window.gapi.client
98 | .request({
99 | path: 'https://people.googleapis.com/v1/people/me/connections',
100 | params: {
101 | personFields: 'names,emailAddresses',
102 | pageSize: maxResults > 1000 ? 1000 : maxResults,
103 | ...(pageToken && { pageToken })
104 | }
105 | })
106 | .then(
107 | response => this.handleNextDataFetch(response, tokenResponse),
108 | err => onFailure(err)
109 | )
110 | }
111 | }
112 |
113 | handleNextDataFetch(response, authResponse) {
114 | const { maxResults } = this.props
115 | // Parse the response body
116 | const parsedData = JSON.parse(response.body)
117 | // Store the fetched data so that we can use it later
118 | this.allData = [...this.allData, ...parsedData.connections]
119 | // If we have more data and the number of data we fethced is less than maxResults then fetch again using the nextPageToken
120 |
121 | if ('nextPageToken' in parsedData && maxResults > this.allData.length) {
122 | this.handleImportContacts(authResponse, parsedData.nextPageToken)
123 | } else {
124 | this.handleParseContacts()
125 | }
126 | }
127 |
128 | handleParseContacts() {
129 | const { onSuccess, onFailure } = this.props
130 | const results = []
131 | try {
132 | for (let index = 0; index < this.allData.length; index += 1) {
133 | const element = this.allData[index]
134 |
135 | if (element.emailAddresses && element.emailAddresses.length > 0) {
136 | results.push({
137 | email: element.emailAddresses[0].value,
138 | title: 'names' in element ? element.names[0].displayName : element.emailAddresses[0].value
139 | })
140 | }
141 | }
142 | onSuccess(results)
143 | } catch (error) {
144 | onFailure('Error to fetch contacts')
145 | }
146 | }
147 |
148 | signIn(e) {
149 | this.allData = []
150 | const { disable } = this.state
151 | const { prompt, onRequest, accessType, onSuccess } = this.props
152 |
153 | onRequest()
154 |
155 | if (e) {
156 | e.preventDefault() // to prevent submit if used within form
157 | }
158 |
159 | if (!disable) {
160 | if (accessType === 'online') {
161 | this.client.callback = resp => {
162 | this.handleImportContacts(resp)
163 | }
164 |
165 | if (window.gapi.client.getToken() === null) {
166 | this.client.requestAccessToken({ prompt })
167 | } else {
168 | this.client.requestAccessToken({ prompt: '' })
169 | }
170 | }
171 |
172 | if (accessType === 'offline') {
173 | this.client.callback = onSuccess
174 |
175 | if (window.gapi.client.getToken() === null) {
176 | this.client.requestCode({ prompt })
177 | } else {
178 | this.client.requestCode({ prompt: '' })
179 | }
180 | }
181 | }
182 | }
183 |
184 | render() {
185 | const { tag, type, className, disabledStyle, buttonText, children, render, theme, icon, disabled: disabledProps } = this.props
186 | const { active, hovered, disabled: disabledState } = this.state
187 | const disabled = disabledState || disabledProps
188 |
189 | if (render) {
190 | return render({ onClick: this.signIn })
191 | }
192 |
193 | const initialStyle = {
194 | backgroundColor: theme === 'dark' ? 'rgb(66, 133, 244)' : '#fff',
195 | display: 'inline-flex',
196 | alignItems: 'center',
197 | color: theme === 'dark' ? '#fff' : 'rgba(0, 0, 0, .54)',
198 | boxShadow: '0 2px 2px 0 rgba(0, 0, 0, .24), 0 0 1px 0 rgba(0, 0, 0, .24)',
199 | padding: 0,
200 | borderRadius: 2,
201 | border: '1px solid transparent',
202 | fontSize: 14,
203 | fontWeight: '500',
204 | fontFamily: 'Roboto, sans-serif'
205 | }
206 |
207 | const hoveredStyle = {
208 | cursor: 'pointer',
209 | opacity: 0.9
210 | }
211 |
212 | const activeStyle = {
213 | cursor: 'pointer',
214 | backgroundColor: theme === 'dark' ? '#3367D6' : '#eee',
215 | color: theme === 'dark' ? '#fff' : 'rgba(0, 0, 0, .54)',
216 | opacity: 1
217 | }
218 |
219 | const defaultStyle = (() => {
220 | if (disabled) {
221 | return Object.assign({}, initialStyle, disabledStyle)
222 | }
223 |
224 | if (active) {
225 | if (theme === 'dark') {
226 | return Object.assign({}, initialStyle, activeStyle)
227 | }
228 |
229 | return Object.assign({}, initialStyle, activeStyle)
230 | }
231 |
232 | if (hovered) {
233 | return Object.assign({}, initialStyle, hoveredStyle)
234 | }
235 |
236 | return initialStyle
237 | })()
238 | const googleLoginButton = React.createElement(
239 | tag,
240 | {
241 | onMouseEnter: () => this.setState({ hovered: true }),
242 | onMouseLeave: () => this.setState({ hovered: false, active: false }),
243 | onMouseDown: () => this.setState({ active: true }),
244 | onMouseUp: () => this.setState({ active: false }),
245 | onClick: this.signIn,
246 | style: defaultStyle,
247 | type,
248 | disabled,
249 | className
250 | },
251 | [
252 | icon && ,
253 |
254 | {children || buttonText}
255 |
256 | ]
257 | )
258 |
259 | return googleLoginButton
260 | }
261 | }
262 |
263 | GoogleContacts.propTypes = {
264 | accessType: PropTypes.string,
265 | buttonText: PropTypes.node,
266 | children: PropTypes.node,
267 | className: PropTypes.string,
268 | clientId: PropTypes.string.isRequired,
269 | disabled: PropTypes.bool,
270 | disabledStyle: PropTypes.object,
271 | hostedDomain: PropTypes.string,
272 | icon: PropTypes.bool,
273 | loginHint: PropTypes.string,
274 | maxResults: PropTypes.number,
275 | onFailure: PropTypes.func.isRequired,
276 | onRequest: PropTypes.func,
277 | onSuccess: PropTypes.func.isRequired,
278 | prompt: PropTypes.string,
279 | redirectUri: PropTypes.string,
280 | render: PropTypes.func,
281 | tag: PropTypes.string,
282 | theme: PropTypes.string,
283 | type: PropTypes.string,
284 | uxMode: PropTypes.string
285 | }
286 |
287 | GoogleContacts.defaultProps = {
288 | accessType: 'online',
289 | buttonText: 'Import from Gmail',
290 | disabled: false,
291 | disabledStyle: {
292 | opacity: 0.6
293 | },
294 | icon: true,
295 | maxResults: 999,
296 | onRequest: () => {},
297 | prompt: 'consent',
298 | tag: 'button',
299 | theme: 'light',
300 | type: 'button',
301 | uxMode: 'popup'
302 | }
303 |
304 | export default GoogleContacts
305 |
--------------------------------------------------------------------------------
/src/icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function Icon({ active }) {
4 | return (
5 |
13 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './google-contacts'
2 |
--------------------------------------------------------------------------------
/webpack/uglify.json:
--------------------------------------------------------------------------------
1 | {
2 | "uglifyOptions": {
3 | "ie8": false,
4 | "debug": false,
5 | "sourceMap": false,
6 | "warnings": false,
7 | "compress": {
8 | "unsafe_comps": true,
9 | "properties": true,
10 | "keep_fargs": false,
11 | "pure_getters": true,
12 | "collapse_vars": true,
13 | "unsafe": true,
14 | "sequences": true,
15 | "dead_code": true,
16 | "drop_debugger": true,
17 | "comparisons": true,
18 | "conditionals": true,
19 | "evaluate": true,
20 | "booleans": true,
21 | "loops": true,
22 | "unused": true,
23 | "hoist_funs": true,
24 | "if_return": true,
25 | "join_vars": true,
26 | "drop_console": false,
27 | "pure_funcs": ["classCallCheck", "invariant", "warning"]
28 | },
29 |
30 | "parallel": true,
31 | "output": {
32 | "comments": false
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/webpack/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 |
4 | const fileRoot = process.cwd()
5 |
6 | module.exports = {
7 | target: 'web',
8 | mode: 'development',
9 | devtool: 'eval',
10 | entry: {
11 | app: ['react-hot-loader/patch', 'webpack/hot/only-dev-server', './demo/index.js']
12 | },
13 | output: {
14 | path: path.join(fileRoot, 'demo'),
15 | filename: 'bundle.js',
16 | publicPath: '/'
17 | },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js$/,
22 | use: {
23 | loader: 'babel-loader',
24 | options: {
25 | cacheDirectory: true,
26 | babelrc: false,
27 | presets: [
28 | '@babel/preset-react',
29 | [
30 | '@babel/preset-env',
31 | {
32 | targets: {
33 | esmodules: false
34 | }
35 | }
36 | ]
37 | ],
38 | plugins: ['react-hot-loader/babel']
39 | }
40 | }
41 | }
42 | ]
43 | },
44 | plugins: [
45 | new webpack.HotModuleReplacementPlugin(),
46 | new webpack.DefinePlugin({
47 | 'process.env.NODE_ENV': JSON.stringify('development')
48 | })
49 | ],
50 | resolve: {
51 | extensions: ['*', '.js']
52 | },
53 | devServer: {
54 | contentBase: path.join(fileRoot, 'demo'),
55 | historyApiFallback: true,
56 | compress: false,
57 | host: process.env.IP || '0.0.0.0',
58 | port: parseInt(process.env.PORT, 0) || 8080,
59 | hot: true,
60 | open: false,
61 | quiet: false,
62 | noInfo: false,
63 | inline: true,
64 | lazy: false,
65 | headers: {
66 | 'Access-Control-Allow-Origin': '*',
67 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
68 | 'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization'
69 | },
70 | stats: {
71 | colors: true
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/webpack/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const TerserPlugin = require('terser-webpack-plugin')
4 |
5 | const fileRoot = process.cwd()
6 |
7 | module.exports = {
8 | mode: 'production',
9 | entry: './src/index.js',
10 | output: {
11 | path: path.join(fileRoot, 'dist'),
12 | filename: 'google-contacts.js',
13 | libraryTarget: 'umd',
14 | globalObject: 'typeof self !== "undefined" ? self : this',
15 | library: 'GoogleContacts'
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | use: {
22 | loader: 'babel-loader',
23 | options: {
24 | cacheDirectory: true,
25 | babelrc: false,
26 | presets: [
27 | '@babel/preset-react',
28 | [
29 | '@babel/preset-env',
30 | {
31 | targets: {
32 | esmodules: false
33 | }
34 | }
35 | ]
36 | ],
37 | plugins: ['transform-react-remove-prop-types']
38 | }
39 | }
40 | }
41 | ]
42 | },
43 | externals: {
44 | react: 'react',
45 | 'react-dom': 'ReactDOM'
46 | },
47 | resolve: {
48 | extensions: ['.js']
49 | },
50 | optimization: {
51 | minimize: true,
52 | minimizer: [new TerserPlugin()]
53 | },
54 | plugins: [
55 | new webpack.DefinePlugin({
56 | 'process.env.NODE_ENV': JSON.stringify('production')
57 | }),
58 | new webpack.LoaderOptionsPlugin({
59 | minimize: true,
60 | debug: false
61 | }),
62 | new webpack.optimize.AggressiveMergingPlugin(),
63 | new webpack.optimize.ModuleConcatenationPlugin()
64 | ],
65 | performance: {
66 | hints: 'warning'
67 | },
68 | stats: {
69 | errorDetails: true,
70 | assets: true,
71 | children: false,
72 | chunks: false,
73 | hash: false,
74 | modules: false,
75 | publicPath: false,
76 | timings: true,
77 | version: false,
78 | warnings: true,
79 | colors: {
80 | green: '\u001b[32m'
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------