├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── launch.json ├── .yarnrc ├── auth-component.jpg ├── build.js ├── buttons.jpg ├── documentjs.json ├── index.html ├── package.json ├── readme.md ├── src ├── auth-component.js ├── auth-container │ ├── auth-container.js │ ├── auth-container.jsx │ └── auth-container.less ├── auth-demo.html ├── auth-demo.js ├── buttons │ ├── amazon │ │ ├── amazon.js │ │ ├── amazon.jsx │ │ ├── amazon.less │ │ └── amazon.svg │ ├── button.js │ ├── button.jsx │ ├── button.less │ ├── buttons-demo.html │ ├── buttons-demo.js │ ├── dropbox │ │ ├── dropbox.js │ │ ├── dropbox.jsx │ │ ├── dropbox.less │ │ └── dropbox.svg │ ├── evernote │ │ ├── evernote.js │ │ ├── evernote.jsx │ │ ├── evernote.less │ │ └── evernote.svg │ ├── facebook │ │ ├── facebook.js │ │ ├── facebook.jsx │ │ ├── facebook.less │ │ └── facebook.svg │ ├── github │ │ ├── github.js │ │ ├── github.jsx │ │ ├── github.less │ │ └── github.svg │ ├── google │ │ ├── google.js │ │ ├── google.jsx │ │ ├── google.less │ │ └── google.svg │ ├── linkedin │ │ ├── linkedin.js │ │ ├── linkedin.jsx │ │ ├── linkedin.less │ │ └── linkedin.svg │ ├── microsoft │ │ ├── microsoft.js │ │ ├── microsoft.jsx │ │ ├── microsoft.less │ │ └── microsoft.svg │ ├── openid │ │ ├── openid.js │ │ ├── openid.jsx │ │ ├── openid.less │ │ └── openid.svg │ ├── paypal │ │ ├── paypal.js │ │ ├── paypal.jsx │ │ ├── paypal.less │ │ └── paypal.svg │ ├── skype │ │ ├── skype.js │ │ ├── skype.jsx │ │ ├── skype.less │ │ └── skype.svg │ ├── slack │ │ ├── slack.js │ │ ├── slack.jsx │ │ ├── slack.less │ │ └── slack.svg │ ├── stackoverflow │ │ ├── stackoverflow.js │ │ ├── stackoverflow.jsx │ │ ├── stackoverflow.less │ │ └── stackoverflow.svg │ ├── twitter │ │ ├── twitter.js │ │ ├── twitter.jsx │ │ ├── twitter.less │ │ └── twitter.svg │ └── yahoo │ │ ├── yahoo.js │ │ ├── yahoo.jsx │ │ ├── yahoo.less │ │ └── yahoo.svg ├── demo-logo.svg ├── forms │ ├── async-validator │ │ ├── async-validator.js │ │ └── async-validator.jsx │ ├── form-error │ │ ├── form-error.js │ │ └── form-error.jsx │ ├── form │ │ ├── form.js │ │ ├── form.jsx │ │ ├── form_test.js │ │ └── test.html │ ├── forms.less │ ├── local-login │ │ ├── demo.html │ │ ├── demo.js │ │ └── local-login.js │ └── local-signup │ │ ├── demo.html │ │ ├── demo.js │ │ └── local-signup.js ├── index.html ├── tabs │ ├── can-route.js │ ├── tabs.jsx │ └── tabs.less └── utils.js ├── steal-jsx.js ├── test ├── test.html └── test.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Unix-style newlines 2 | [*] 3 | end_of_line = LF 4 | indent_style = tab 5 | trim_trailing_whitespace = false 6 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | docs/ 31 | dist/ 32 | 33 | .DS_Store 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | !dist/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: node 3 | before_install: 4 | - "export DISPLAY=:99.0" 5 | - "sh -e /etc/init.d/xvfb start" -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceRoot}/node_modules/.bin/done-serve", 9 | "stopOnEntry": false, 10 | "args": [ "--static", "--develop", "--port", "8999"], 11 | "cwd": "${workspaceRoot}", 12 | "preLaunchTask": null, 13 | "runtimeExecutable": null, 14 | "runtimeArgs": [ 15 | "--nolazy" 16 | ], 17 | "env": { 18 | "NODE_ENV": "development" 19 | }, 20 | "console": "internalConsole", 21 | "sourceMaps": false, 22 | "outDir": null 23 | }, 24 | { 25 | "name": "Attach", 26 | "type": "node", 27 | "request": "attach", 28 | "port": 5858, 29 | "address": "localhost", 30 | "restart": false, 31 | "sourceMaps": false, 32 | "outDir": null, 33 | "localRoot": "${workspaceRoot}", 34 | "remoteRoot": null 35 | }, 36 | { 37 | "name": "Attach to Process", 38 | "type": "node", 39 | "request": "attach", 40 | "processId": "${command.PickProcess}", 41 | "port": 5858, 42 | "sourceMaps": false, 43 | "outDir": null 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | ignore-engines true -------------------------------------------------------------------------------- /auth-component.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icanjs/auth-component/604114e1ee69a0ff4666a4c123a0182dee2d75ab/auth-component.jpg -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var stealTools = require('steal-tools'); 2 | var path = require('path'); 3 | 4 | stealTools.export({ 5 | steal: { 6 | config: path.join(__dirname, '/package.json!npm') 7 | }, 8 | outputs: { 9 | '+cjs': {}, 10 | '+amd': {}, 11 | '+global-js': {} 12 | } 13 | }).catch(function (e) { 14 | setTimeout(function () { 15 | throw e; 16 | }, 1); 17 | }); 18 | -------------------------------------------------------------------------------- /buttons.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icanjs/auth-component/604114e1ee69a0ff4666a4c123a0182dee2d75ab/buttons.jpg -------------------------------------------------------------------------------- /documentjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "sites": { 3 | "docs": { 4 | "dest": "docs", 5 | "parent": "auth-component", 6 | "pageConfig": { 7 | "page": "docs" 8 | }, 9 | "glob": { 10 | "pattern": "src/**/*.{js,md}" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Auth Component Demo 4 | 5 | 6 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-component", 3 | "version": "5.0.13", 4 | "description": "Clean authentication components for React", 5 | "homepage": "https://github.com/icanjs/auth-component", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/icanjs/auth-component.git" 9 | }, 10 | "author": { 11 | "name": "Marshall Thompson", 12 | "email": "marshall@creativeideal.net", 13 | "url": "https://github.com/marshallswain" 14 | }, 15 | "scripts": { 16 | "prepublish": "npm run build", 17 | "publish": "git push origin --tags && git push origin", 18 | "testee": "testee test/test.html --browsers firefox", 19 | "test": "npm run lint && npm run testee", 20 | "lint": "semistandard --fix", 21 | "release:patch": "npm version patch && npm publish", 22 | "release:minor": "npm version minor && npm publish", 23 | "release:major": "npm version major && npm publish", 24 | "build": "node build.js", 25 | "document": "documentjs", 26 | "develop": "done-serve --static --develop --port 8080" 27 | }, 28 | "main": "dist/cjs/auth-component", 29 | "keywords": [ 30 | "canjs", 31 | "react", 32 | "component", 33 | "donejs", 34 | "authentication" 35 | ], 36 | "steal": { 37 | "main": "auth-component", 38 | "configDependencies": [ 39 | "live-reload" 40 | ], 41 | "npmIgnore": [ 42 | "documentjs", 43 | "testee", 44 | "generator-donejs", 45 | "donejs-cli", 46 | "steal-tools" 47 | ], 48 | "plugins": [ 49 | "steal-css", 50 | "steal-less", 51 | "steal-stache", 52 | "steal-react-jsx", 53 | "steal-svg" 54 | ], 55 | "directories": { 56 | "lib": "src" 57 | } 58 | }, 59 | "dependencies": { 60 | "can-define": "^1.0.8", 61 | "feathers-authentication-popups": "^0.1.2", 62 | "lodash.debounce": "^4.0.8", 63 | "lodash.isequal": "^4.5.0", 64 | "react": "^15.4.1", 65 | "react-dom": "^15.4.1", 66 | "react-form": "^1.0.0-beta.1", 67 | "react-svg-inline": "^1.2.0", 68 | "react-view-models": "^0.0.7" 69 | }, 70 | "devDependencies": { 71 | "assert": "^1.4.1", 72 | "can-route": "^3.0.6", 73 | "can-route-react": "^0.1.2", 74 | "done-serve": "^0.2.0", 75 | "semistandard": "^9.1.0", 76 | "steal": "^1.0.4", 77 | "steal-css": "^1.0.0", 78 | "steal-less": "^1.0.1", 79 | "steal-mocha": "^1.0.0", 80 | "steal-react-jsx": "^0.0.3", 81 | "steal-svg": "^0.0.5", 82 | "steal-tools": "^1.0.1", 83 | "testee": "^0.3.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # auth-component 2 | 3 | [![Build Status](https://travis-ci.org/icanjs/auth-component.png?branch=master)](https://travis-ci.org/icanjs/auth-component) 4 | 5 | Compose a clean Auth UI with these simple React components. 6 | 7 | The CanJS Stache version: https://github.com/icanjs/can-auth-component
8 | The Vue.js version: https://github.com/icanjs/vue-auth-component 9 | 10 | ![auth-component example](https://cloud.githubusercontent.com/assets/128857/21478355/da76f80a-cb07-11e6-8a6d-dc382d30bf9f.jpg) 11 | 12 | ## Example Usage 13 | 14 | `auth-component` is a collection of components. They can be composed based on the auth requirements of your application. The main demo shows how to build the example shown in the image above. To run the demo, start an `http-server` in the root and open [http://localhost:8080](http://localhost:8080). Here's the [demo code](https://github.com/icanjs/auth-component/blob/master/src/auth-demo.js). 15 | 16 | ```jsx 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 |
35 | ``` 36 | 37 | ## Quick and Easy Horizontal and Vertical Centering 38 | 39 | The `` component is a set a styles that center a white login container both vertically and horizontally inside its parent element. It has no viewModel logic of its own, so all of the other components will work without it. 40 | 41 | ```jsx 42 | import AuthContainer from 'auth-component/auth-container/auth-container'; 43 | // In your template. 44 | 45 | Put whatever markup you want inside here. 46 | 47 | ``` 48 | 49 | ## Local Auth Forms, Ready For Use 50 | 51 | A basic Local (username & password) Login and Signup form are included. 52 | 53 | ```jsx 54 | import SignupForm from 'auth-component/forms/local-signup/local-signup'; 55 | import LoginForm from 'auth-component/forms/local-login/local-login'; 56 | ``` 57 | 58 | Check out the [local-login demo](https://github.com/icanjs/auth-component/blob/master/src/forms/local-login/local-login.js) and [local-signup demo](https://github.com/icanjs/auth-component/blob/master/src/forms/local-signup/local-signup.js) code to see example usage. 59 | 60 | The following attributes are available in both forms: 61 | 62 | - `usernameField` {String} Allows you to customize one of the attributes sent to the server. It's set to `"email"` by default. 63 | - `usernamePlaceholder` {String} Set the placeholder text for the `usernameField`. Default is `"e-mail address"`. 64 | - `passwordField` {String} Allows you to customize an attribute sent to the server. The default is `"password"`. 65 | - `passwordPlaceholder` {String} Set the placeholder text for the `passwordField`. Default is `"password"`. 66 | - `strategy` {String} When using [feathers-authentication](https://github.com/feathersjs/feathers-authentication), setting this attribute will add a `strategy` attribute to the outgoing data. 67 | - `Model` {can-connect Model} a can-connect compatible Model to use for submitting the form data. 68 | - `service` {FeathersJS service} a Feathers service to use for submitting the form data. 69 | - `suppressWarnings` {Boolean} There are a few warnings that will show up by default. Turn them off by setting `suppressWarnings` to true. Default `false`. 70 | - `error` {String} When the server responds with an error string or an error object containing a `message` string, it will be set on `error` and shown in the UI above the form. 71 | - `buttonText` {String} Set the main action button's label. Default is `"Login"` or `"Signup"`. 72 | - `clearError` {Function} Clears the error message. 73 | - `onSubmit(data)` {Function} is called with the form data when the form is submitted. If a `Model` or `service` was provided, it will be used to communicate with the server. If not, `handleSubmit` must be overwritten with your own logic. It must return a `Promise`. 74 | - `onSuccess(responseData)` {Function} is called with the server response data. 75 | - `onError(error)` {Function} is called with the server response error. 76 | 77 | As of version `5.0`, both forms are based off of [@tannerlinsley/react-form](https://github.com/tannerlinsley/react-form). Check out the [React-Form API docs](https://github.com/tannerlinsley/react-form#-form-) to see additional properties and functions that are available. 78 | 79 | These are the custom attributes for the `` form: 80 | - `onForgot` {Function} runs when the user clicks the "Forgot Password" link. There is no default handler for this, so you have to provide your own function. 81 | 82 | These are the custom attributes for the `` form: 83 | - `asyncValidation` {Function} A function that returns a promise. If an error string is returned, or an error object with a `message` string is returned, it will become the validation error for the username/email field. 84 | 85 | See the "Running the Demos" section to run the included form demos. Both demos include examples for using a `Model`, `service`, or custom function. 86 | 87 | ## Create Custom Forms 88 | 89 | As of version `5.0`, and as part of the refactor to use [react-form](https://github.com/tannerlinsley/react-form), you can easily create your own auth form. The `Form` element is a wrapper for the react-form component by the same name, but adds asynchronous validation support and automatic server response error handling. The following properties are available on the `Form` component: 90 | 91 | - `strategy` {String} When using [feathers-authentication](https://github.com/feathersjs/feathers-authentication), setting this attribute will add a `strategy` attribute to the outgoing data. 92 | - `Model` {can-connect Model} a can-connect compatible Model to use for submitting the form data. 93 | - `service` {FeathersJS service} a Feathers service to use for submitting the form data. 94 | - `suppressWarnings` {Boolean} There are a few warnings that will show up by default. Turn them off by setting `suppressWarnings` to true. Default `false`. 95 | - `error` {String} When the server responds with an error string or an error object containing a `message` string, it will be set on `error` and shown in the UI above the form. 96 | - `clearError` {Function} Clears the error message. 97 | - `onSubmit(data)` {Function} is called with the form data when the form is submitted. If a `Model` or `service` was provided, it will be used to communicate with the server. If not, `handleSubmit` must be overwritten with your own logic. It must return a `Promise`. 98 | - `onSuccess(responseData)` {Function} is called with the server response data. 99 | - `onError(error)` {Function} is called with the server response error. 100 | 101 | Check out the [React-Form API docs](https://github.com/tannerlinsley/react-form#-form-) to see additional properties and functions that are available. Below is an annotated example of how to make a custom form. 102 | 103 | ```jsx 104 | import React from 'react'; 105 | import Form from '../form/form.js'; 106 | import { Text } from 'react-form'; 107 | import '../forms.less'; 108 | import FormError from '../form-error/form-error'; 109 | import AsyncValidator from '../async-validator/async-validator'; 110 | 111 | export default ({ 112 | asyncValidation, 113 | forgotClicked, 114 | // Allow all react-form props to pass through 115 | ...rest 116 | }) => { 117 | return ( 118 |
119 | {// You must wrap your custom form in two functions as done here.} 120 | {({error, clearError}) => { 121 | return ({values, submitForm}) => { 122 | return ( 123 | 124 | 125 | 126 | 127 | 128 | 129 | {asyncValidation && } 130 | 131 | 132 | 133 |
134 | forgot password 135 |
136 | 137 | 138 | 139 | ); 140 | }; 141 | }} 142 | 143 | ); 144 | }; 145 | ``` 146 | 147 | Any `react-form` fields you add will be added to the payload and sent to the server. 148 | 149 | ## Asynchronous Field Validation 150 | The `AsyncValidator` component allows you to run asynchronous validations against a server. The `Form` example, above, shows how to use it in a form. To make the validations work, you need to use the `validate` attribute on a form. We assigned the AsyncValidator a `field` of `emailError`. Now we can use the `emailError` attribute in the `validate` rules: 151 | 152 | ```js 153 | { 157 | return { 158 | email: !email ? 'E-mail address is required' : emailError || null, 159 | password: !password ? 'Password is required' : null 160 | }; 161 | }} 162 | onSuccess={handleSuccess} 163 | usernameField='username' 164 | usernamePlaceholder='username' 165 | asyncValidation={simulatedAsyncValidation} /> 166 | 167 | function simulatedAsyncValidation (query) { 168 | return new Promise((resolve, reject) => { 169 | setTimeout(() => { 170 | if (query.email === 'contact@bitovi.com') { 171 | reject('That email is unavailable'); 172 | } else { 173 | resolve(true); 174 | } 175 | }, 500); 176 | }); 177 | } 178 | ``` 179 | 180 | ## Automatic Form Error Handling 181 | 182 | The `FormError` component is simply a `div` with an error message in it. It is used to show error messages returned from a server. See how it's used in the `Form` example, above, or in the demos. When used with the `Form` component, errors shows when returned from the server. They are automatically cleared when the form is submitted. 183 | 184 | ```js 185 | import FormError from 'auth-component/forms/form-error/form-error'; 186 | 187 | 188 | ``` 189 | 190 | - `error` {String} The error message to display. 191 | - `clearError` {Function} a function that can be called to clear the error message. 192 | 193 | ## Beautiful, Scalable Buttons 194 | 195 | A Generic button and a bunch of hand-tailored, scalable buttons are included. 196 | 197 | ### Generic Auth Button 198 | 199 | The generic button is the base for all of the other buttons. You can use it to make your own auth buttons. Here's how the Facebook button implements the generic button: 200 | 201 | ```jsx 202 | import React from 'react'; 203 | import AuthButton from '../button.jsx'; 204 | import svg from './facebook.svg'; 205 | 206 | export default ({name, url, img, alt, text, popup}) => { 207 | return ( 208 | 215 | ); 216 | }; 217 | ``` 218 | 219 | - `url` is like specifying the `href` on a link. The default value matches FeathersJS default OAuth URLs like `/auth/`. For example, the Facebook button uses `/auth/facebook`. 220 | - `popup`, if truthy, simply opens the `url` in a centered popup window. 221 | - `alt` is for alt text, the same as on other HTML elements. 222 | - `text` allows you to specify some text to the right of the image. 223 | - `svg` allows you to embed svg directly into the button. 224 | - `img` is supported in place of `svg`. The `img` attribute should the the URL to an image. 225 | 226 | ### Ready-to-use Buttons 227 | 228 | A bunch of pre-styled buttons are included. They all extend the generic button. 229 | 230 | ```js 231 | import Amazon from 'auth-component/buttons/amazon/amazon'; 232 | import Dropbox from 'auth-component/buttons/dropbox/dropbox'; 233 | import Evernote from 'auth-component/buttons/evernote/evernote'; 234 | import Facebook from 'auth-component/buttons/facebook/facebook'; 235 | import Github from 'auth-component/buttons/github/github'; 236 | import Google from 'auth-component/buttons/google/google'; 237 | import LinkedIn from 'auth-component/buttons/linkedin/linkedin'; 238 | import Microsoft from 'auth-component/buttons/microsoft/microsoft'; 239 | import OpenID from 'auth-component/buttons/openid/openid'; 240 | import PayPal from 'auth-component/buttons/paypal/paypal'; 241 | import Skype from 'auth-component/buttons/skype/skype'; 242 | import Slack from 'auth-component/buttons/slack/slack'; 243 | import StackOverflow from 'auth-component/buttons/stackoverflow/stackoverflow'; 244 | import Twitter from 'auth-component/buttons/twitter/twitter'; 245 | import Yahoo from 'auth-component/buttons/yahoo/yahoo'; 246 | ``` 247 | 248 | You'll generally only ever have to specify the `url`, `text`, and `popup` attributes. 249 | 250 | ```jsx 251 | import FacebookButton from 'auth-component/buttons/facebook/facebook'; 252 | 253 | 254 | ``` 255 | 256 | If you don't specify a `text` attribute, you'll get a square button with an icon. The button with `text` from the above code would look like the "Login with Facebook" button in this example: 257 | 258 | ![AuthComponent Buttons Demo](https://cloud.githubusercontent.com/assets/128857/21478412/70751af8-cb08-11e6-8305-807c6fd0777b.jpg) 259 | 260 | ## Tabs 261 | 262 | Currently, the only set of tabs uses [can-route](https://github.com/canjs/can-route) to change tabs. If the feature is needed, [this issue for creating a standalone set of tabs](https://github.com/icanjs/auth-component/issues/18) is open and could use a champion. 263 | 264 | The main demo shows how to use can-route based tabs together. You first need a basic can-route setup, shown in the below example. Then you can use the `` component from [can-route-react](https://github.com/icanjs/can-route-react) to show and hide components. 265 | 266 | ```jsx 267 | import React from 'react'; 268 | import ReactDOM from 'react-dom'; 269 | import route from 'can-route'; 270 | import DefineMap from 'can-define/map/map'; 271 | import {Route} from 'can-route-react'; 272 | 273 | import AuthContainer from './auth-container/auth-container'; 274 | import Tabs from 'auth-component/tabs/can-route'; 275 | import SignupForm from 'auth-component/forms/signup/'; 276 | import LoginForm from 'auth-component/forms/login/'; 277 | 278 | const RouteMap = DefineMap.extend({ 279 | page: { 280 | type: 'string' 281 | } 282 | }); 283 | route.data = new RouteMap({}); 284 | 285 | // Create a '/page' route. 286 | route('{page}', {page: 'login'}); 287 | route.ready(); 288 | 289 | ReactDOM.render( 290 | 291 | 292 | 293 | 294 | 295 | , 296 | document.querySelector('[root=true]') 297 | ); 298 | ``` 299 | 300 | ## Changelog 301 | - `5.0.0` - Rebuilt forms using [tannerlinsley/react-form](https://github.com/tannerlinsley/react-form). 302 | - Forms can now be validated. 303 | - It's now MUCH easier to customize forms. You're no longer stuck using the basic login forms, which only include email and password fields. 304 | - Added AsyncValidator component that works with React-Form. 305 | - Added FormError component that shows server-sent form errors. 306 | - `4.0.0` 307 | - Created login buttons. 308 | - Created basic login and signup forms. No validation. 309 | 310 | ## Contributing 311 | 312 | ### Running the demos 313 | You can try out the included demos using the following steps: 314 | 315 | 1. Clone the repo. 316 | 2. Run `yarn` or `npm install` 317 | 3. Run `npm run develop` 318 | 4. With the development server running, open a demo 319 | - [Main demo](http://localhost:8080) 320 | - [Local Login Form Demo](http://localhost:8080/src/forms/local-login/demo.html) 321 | - [Local Signup Form Demo](http://localhost:8080/src/forms/local-signup/demo.html) 322 | 323 | ### Making a Build 324 | 325 | To make a build of the distributables into `dist/` in the cloned repository run 326 | 327 | ``` 328 | npm install 329 | node build 330 | ``` 331 | 332 | ### Running the tests 333 | 334 | Tests can run in the browser by opening a webserver and visiting the `test/test.html` page. 335 | Automated tests that run the tests from the command line in Firefox can be run with 336 | 337 | ``` 338 | npm test 339 | ``` 340 | -------------------------------------------------------------------------------- /src/auth-component.js: -------------------------------------------------------------------------------- 1 | import AuthContainer from './auth-container/auth-container'; 2 | 3 | import Form from './forms/form/form'; 4 | import FormError from './forms/form-error/form-error'; 5 | import AsyncValidator from './forms/async-validator/async-validator'; 6 | import LocalLoginForm from './forms/local-login/local-login'; 7 | import LocalSignupForm from './forms/local-signup/local-signup'; 8 | 9 | import Generic from './buttons/button'; 10 | import Amazon from './buttons/amazon/amazon'; 11 | import Dropbox from './buttons/dropbox/dropbox'; 12 | import Evernote from './buttons/evernote/evernote'; 13 | import Facebook from './buttons/facebook/facebook'; 14 | import Github from './buttons/github/github'; 15 | import Google from './buttons/google/google'; 16 | import LinkedIn from './buttons/linkedin/linkedin'; 17 | import Microsoft from './buttons/microsoft/microsoft'; 18 | import OpenID from './buttons/openid/openid'; 19 | import PayPal from './buttons/paypal/paypal'; 20 | import Skype from './buttons/skype/skype'; 21 | import Slack from './buttons/slack/slack'; 22 | import StackOverflow from './buttons/stackoverflow/stackoverflow'; 23 | import Twitter from './buttons/twitter/twitter'; 24 | import Yahoo from './buttons/yahoo/yahoo'; 25 | 26 | import CanRouteTabs from './tabs/can-route'; 27 | 28 | export { 29 | AuthContainer, 30 | Form, 31 | FormError, 32 | AsyncValidator, 33 | LocalLoginForm, 34 | LocalSignupForm, 35 | Generic as GenericButton, 36 | Amazon as AmazonButton, 37 | Dropbox as DropboxButton, 38 | Evernote as EvernoteButton, 39 | Facebook as FacebookButton, 40 | Github as GithubButton, 41 | Google as GoogleButton, 42 | LinkedIn as LinkedInButton, 43 | Microsoft as MicrosoftButton, 44 | OpenID as OpenIDButton, 45 | PayPal as PayPalButton, 46 | Skype as SkypeButton, 47 | Slack as SlackButton, 48 | StackOverflow as StackOverflowButton, 49 | Twitter as TwitterButton, 50 | Yahoo as YahooButton, 51 | CanRouteTabs 52 | }; 53 | 54 | export default { 55 | AuthContainer, 56 | Form, 57 | FormError, 58 | AsyncValidator, 59 | LocalLoginForm, 60 | LocalSignupForm, 61 | buttons: { 62 | Generic, 63 | Amazon, 64 | Dropbox, 65 | Evernote, 66 | Facebook, 67 | Github, 68 | Google, 69 | LinkedIn, 70 | Microsoft, 71 | OpenID, 72 | PayPal, 73 | Skype, 74 | Slack, 75 | StackOverflow, 76 | Twitter, 77 | Yahoo 78 | }, 79 | CanRouteTabs 80 | }; 81 | -------------------------------------------------------------------------------- /src/auth-container/auth-container.js: -------------------------------------------------------------------------------- 1 | import container from './auth-container.jsx'; 2 | import './auth-container.less'; 3 | 4 | export default container; 5 | -------------------------------------------------------------------------------- /src/auth-container/auth-container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({children}) => { 4 | return ( 5 |
6 |
{children}
7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/auth-container/auth-container.less: -------------------------------------------------------------------------------- 1 | div.auth-component { 2 | display: block; 3 | color: #4A4A4A; 4 | width: 100%; 5 | .bp-mobile-medium({ 6 | justify-content: center; 7 | align-items: center; 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: center; 11 | }); 12 | 13 | a { 14 | text-decoration: none; 15 | } 16 | 17 | div.auth-ui-container { 18 | padding-bottom: 11px; 19 | background: white; 20 | .bp-mobile-medium({ 21 | -webkit-box-shadow: 0px 0px 24px 0px rgba(110,110,110,0.75); 22 | -moz-box-shadow: 0px 0px 24px 0px rgba(110,110,110,0.75); 23 | box-shadow: 0px 0px 24px 0px rgba(110,110,110,0.75); 24 | width: 290px; 25 | }); 26 | } 27 | } 28 | 29 | .auth-branding { 30 | padding: 30px 0; 31 | text-align: center; 32 | } 33 | 34 | .auth-error { 35 | text-align: center; 36 | background: #FFDCDC; 37 | border: 1px solid #FF0000; 38 | font-weight: 500; 39 | margin: 0 15px; 40 | padding: 4px; 41 | font-size: 14px; 42 | color: #FF0A0A; 43 | } 44 | 45 | /* MEDIUM MOBILE DEVICES ~375px+ */ 46 | .bp-mobile-medium(@rules) { 47 | @media only screen and (min-width: 375px) { @rules(); } 48 | } 49 | 50 | /* LARGE MOBILE DEVICES ~425px+ */ 51 | .bp-mobile-large(@rules) { 52 | @media only screen and (min-width: 425px) { @rules(); } 53 | } 54 | 55 | /* TABLET & SMALLER LAPTOPS ~768px+ */ 56 | .bp-tablet(@rules) { 57 | @media only screen and (min-width: 768px) { @rules(); } 58 | } 59 | 60 | /* DESKTOP ~1030px+ */ 61 | .bp-desktop(@rules) { 62 | @media only screen and (min-width: 1030px) { @rules(); } 63 | } 64 | 65 | /* LARGE VIEWING SIZE ~1240px+ */ 66 | .bp-large-screen(@rules) { 67 | @media only screen and (min-width: 1240px) { @rules(); } 68 | } -------------------------------------------------------------------------------- /src/auth-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Auth Component Demo 4 | 5 | 6 | 17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/auth-demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import route from 'can-route'; 4 | import DefineMap from 'can-define/map/map'; 5 | import {Route} from 'can-route-react'; 6 | 7 | import SVGInline from 'react-svg-inline'; 8 | import logo from './demo-logo.svg'; 9 | 10 | import AuthContainer from './auth-container/auth-container'; 11 | import Tabs from './tabs/can-route'; 12 | import FacebookButton from './buttons/facebook/'; 13 | import GitHubButton from './buttons/github/'; 14 | import GoogleButton from './buttons/google/'; 15 | import MicrosoftButton from './buttons/microsoft/'; 16 | import TwitterButton from './buttons/twitter/'; 17 | import SignupForm from 'auth-component/forms/local-signup/local-signup'; 18 | import LoginForm from 'auth-component/forms/local-login/local-login'; 19 | 20 | const RouteMap = DefineMap.extend({ 21 | page: { 22 | type: 'string' 23 | } 24 | }); 25 | route.data = new RouteMap({}); 26 | 27 | // Create a '/page' route. 28 | route('{page}', {page: 'login'}); 29 | 30 | route.ready(); 31 | 32 | ReactDOM.render( 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 49 | 50 |
, 51 | document.querySelector('[root=true]') 52 | ); 53 | -------------------------------------------------------------------------------- /src/buttons/amazon/amazon.js: -------------------------------------------------------------------------------- 1 | import AmazonButton from './amazon.jsx'; 2 | import './amazon.less'; 3 | 4 | export default AmazonButton; 5 | -------------------------------------------------------------------------------- /src/buttons/amazon/amazon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button'; 3 | import './amazon.less'; 4 | import svg from './amazon.svg'; 5 | 6 | export default ({name, url, img, alt, text, popup}) => { 7 | return ( 8 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/buttons/amazon/amazon.less: -------------------------------------------------------------------------------- 1 | .oauth-amazon { 2 | background: #FFAD1D; 3 | > * { 4 | color: black; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 154px; 10 | } 11 | span { 12 | font-weight: 400; 13 | } 14 | } -------------------------------------------------------------------------------- /src/buttons/amazon/amazon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | amazon 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/button.js: -------------------------------------------------------------------------------- 1 | import Button from './button.jsx'; 2 | import './button.less'; 3 | 4 | export default Button; 5 | -------------------------------------------------------------------------------- /src/buttons/button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import openPopup from 'feathers-authentication-popups/handler'; 3 | import SVGInline from 'react-svg-inline'; 4 | import './button.less'; 5 | 6 | export default ({name, classSuffix, url, img, alt, text, popup, svg}) => { 7 | return ( 8 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/button.less: -------------------------------------------------------------------------------- 1 | div.oauth-buttons { 2 | margin: 0; 3 | padding: 0; 4 | display: flex; 5 | justify-content: center; 6 | flex-wrap: wrap; 7 | } 8 | 9 | button.auth-button { 10 | border: 0px solid #556D99; 11 | border-radius: 2px; 12 | padding: 0; 13 | margin: 0.4em; 14 | font-size: 12px; 15 | 16 | a { 17 | display: flex; 18 | align-items: center; 19 | padding: 0.5em 0; 20 | text-decoration: none; 21 | 22 | span.SVGInline { 23 | width: 28px; 24 | height: 28px; 25 | margin: 0 8px; 26 | display: inline-block; 27 | svg { 28 | height: 100%; 29 | width: 100%; 30 | } 31 | } 32 | img.logo { 33 | height: 28px; 34 | margin: 0 8px; 35 | } 36 | } 37 | a:hover { 38 | text-decoration: underline; 39 | } 40 | span.auth-button-text { 41 | padding-left: 2px; 42 | white-space: nowrap; 43 | margin-right: 0.8em; 44 | } 45 | } -------------------------------------------------------------------------------- /src/buttons/buttons-demo.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /src/buttons/buttons-demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AmazonButton from './amazon/amazon'; 4 | import DropboxButton from './dropbox/dropbox'; 5 | import EvernoteButton from './evernote/evernote'; 6 | import FacebookButton from './facebook/facebook'; 7 | import GithubButton from './github/github'; 8 | import GoogleButton from './google/google'; 9 | import LinkedInButton from './linkedin/linkedin'; 10 | import MicrosoftButton from './microsoft/microsoft'; 11 | import OpenIDButton from './openid/openid'; 12 | import PaypalButton from './paypal/paypal'; 13 | import SkypeButton from './skype/skype'; 14 | import SlackButton from './slack/slack'; 15 | import StackOverflowButton from './stackoverflow/stackoverflow'; 16 | import TwitterButton from './twitter/twitter'; 17 | import YahooButton from './yahoo/yahoo'; 18 | 19 | // Render the DOM 20 | ReactDOM.render( 21 |
22 |

Plain Buttons

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |

Buttons with Text

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
, 56 | document.querySelector('[root=true]') 57 | ); 58 | -------------------------------------------------------------------------------- /src/buttons/dropbox/dropbox.js: -------------------------------------------------------------------------------- 1 | import DropboxButton from './dropbox.jsx'; 2 | import './dropbox.less'; 3 | 4 | export default DropboxButton; 5 | -------------------------------------------------------------------------------- /src/buttons/dropbox/dropbox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './dropbox.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/dropbox/dropbox.less: -------------------------------------------------------------------------------- 1 | .oauth-dropbox { 2 | background: #007EE5; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 154px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/dropbox/dropbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | dropbox 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/buttons/evernote/evernote.js: -------------------------------------------------------------------------------- 1 | import EvernoteButton from './evernote.jsx'; 2 | import './evernote.less'; 3 | 4 | export default EvernoteButton; 5 | -------------------------------------------------------------------------------- /src/buttons/evernote/evernote.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './evernote.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/evernote/evernote.less: -------------------------------------------------------------------------------- 1 | .oauth-evernote { 2 | background: #68B629; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 149px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/evernote/evernote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | evernote 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/facebook/facebook.js: -------------------------------------------------------------------------------- 1 | import FacebookButton from './facebook.jsx'; 2 | import './facebook.less'; 3 | 4 | export default FacebookButton; 5 | -------------------------------------------------------------------------------- /src/buttons/facebook/facebook.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './facebook.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/facebook/facebook.less: -------------------------------------------------------------------------------- 1 | .oauth-facebook { 2 | background: #5E7AB5; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 159px; 10 | } 11 | } -------------------------------------------------------------------------------- /src/buttons/facebook/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | facebook 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/github/github.js: -------------------------------------------------------------------------------- 1 | import GitHubButton from './github.jsx'; 2 | import './github.less'; 3 | 4 | export default GitHubButton; 5 | -------------------------------------------------------------------------------- /src/buttons/github/github.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './github.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/github/github.less: -------------------------------------------------------------------------------- 1 | .oauth-github { 2 | background: #a6a0a0; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 144px; 10 | } 11 | } -------------------------------------------------------------------------------- /src/buttons/github/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | github 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/google/google.js: -------------------------------------------------------------------------------- 1 | import GoogleButton from './google.jsx'; 2 | import './google.less'; 3 | 4 | export default GoogleButton; 5 | -------------------------------------------------------------------------------- /src/buttons/google/google.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './google.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/google/google.less: -------------------------------------------------------------------------------- 1 | .oauth-google { 2 | background: #DD4B39; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 145px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/google/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | google 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/linkedin/linkedin.js: -------------------------------------------------------------------------------- 1 | import LinkedInButton from './linkedin.jsx'; 2 | import './linkedin.less'; 3 | 4 | export default LinkedInButton; 5 | -------------------------------------------------------------------------------- /src/buttons/linkedin/linkedin.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './linkedin.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/linkedin/linkedin.less: -------------------------------------------------------------------------------- 1 | .oauth-linkedin { 2 | background: #006087; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 148px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/linkedin/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | linkedin 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/buttons/microsoft/microsoft.js: -------------------------------------------------------------------------------- 1 | import MicrosoftButton from './microsoft.jsx'; 2 | import './microsoft.less'; 3 | 4 | export default MicrosoftButton; 5 | -------------------------------------------------------------------------------- /src/buttons/microsoft/microsoft.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './microsoft.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/microsoft/microsoft.less: -------------------------------------------------------------------------------- 1 | .oauth-microsoft { 2 | background: #2672EC; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 153px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/microsoft/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | microsoft 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/openid/openid.js: -------------------------------------------------------------------------------- 1 | import OpenIDButton from './openid.jsx'; 2 | import './openid.less'; 3 | 4 | export default OpenIDButton; 5 | -------------------------------------------------------------------------------- /src/buttons/openid/openid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './openid.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/openid/openid.less: -------------------------------------------------------------------------------- 1 | .oauth-openid { 2 | background: #F27F1C; 3 | > * { 4 | color: black; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 148px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/openid/openid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | openid 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/paypal/paypal.js: -------------------------------------------------------------------------------- 1 | import PaypalButton from './paypal.jsx'; 2 | import './paypal.less'; 3 | 4 | export default PaypalButton; 5 | -------------------------------------------------------------------------------- /src/buttons/paypal/paypal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './paypal.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/paypal/paypal.less: -------------------------------------------------------------------------------- 1 | .oauth-paypal { 2 | background: #005082; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 141px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/paypal/paypal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | paypal 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/buttons/skype/skype.js: -------------------------------------------------------------------------------- 1 | import SkypeButton from './skype.jsx'; 2 | import './skype.less'; 3 | 4 | export default SkypeButton; 5 | -------------------------------------------------------------------------------- /src/buttons/skype/skype.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './skype.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/skype/skype.less: -------------------------------------------------------------------------------- 1 | .oauth-skype { 2 | background: #1AAEF0; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 140px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/skype/skype.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | skype 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/slack/slack.js: -------------------------------------------------------------------------------- 1 | import SlackButton from './slack.jsx'; 2 | import './slack.less'; 3 | 4 | export default SlackButton; 5 | -------------------------------------------------------------------------------- /src/buttons/slack/slack.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './slack.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/slack/slack.less: -------------------------------------------------------------------------------- 1 | .oauth-slack { 2 | background: #50AD83; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 135px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/slack/slack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | slack 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/buttons/stackoverflow/stackoverflow.js: -------------------------------------------------------------------------------- 1 | import StackOverflowButton from './stackoverflow.jsx'; 2 | import './stackoverflow.less'; 3 | 4 | export default StackOverflowButton; 5 | -------------------------------------------------------------------------------- /src/buttons/stackoverflow/stackoverflow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './stackoverflow.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/stackoverflow/stackoverflow.less: -------------------------------------------------------------------------------- 1 | .oauth-stackoverflow { 2 | background: #E5712A; 3 | > * { 4 | color: black; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 179px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/stackoverflow/stackoverflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | stackoverflow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/buttons/twitter/twitter.js: -------------------------------------------------------------------------------- 1 | import TwitterButton from './twitter.jsx'; 2 | import './twitter.less'; 3 | 4 | export default TwitterButton; 5 | -------------------------------------------------------------------------------- /src/buttons/twitter/twitter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './twitter.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/twitter/twitter.less: -------------------------------------------------------------------------------- 1 | .oauth-twitter { 2 | background: #50ABF1; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 147px; 10 | } 11 | } -------------------------------------------------------------------------------- /src/buttons/twitter/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | twitter 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/buttons/yahoo/yahoo.js: -------------------------------------------------------------------------------- 1 | import YahooButton from './yahoo.jsx'; 2 | import './yahoo.less'; 3 | 4 | export default YahooButton; 5 | -------------------------------------------------------------------------------- /src/buttons/yahoo/yahoo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AuthButton from '../button.jsx'; 3 | import svg from './yahoo.svg'; 4 | 5 | export default ({name, url, img, alt, text, popup}) => { 6 | return ( 7 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/buttons/yahoo/yahoo.less: -------------------------------------------------------------------------------- 1 | .oauth-yahoo { 2 | background: #7B0099; 3 | > * { 4 | color: white; 5 | } 6 | 7 | /* This shouldn't know about its implementation. Move this somewhere else. */ 8 | &.one { 9 | width: 142px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/buttons/yahoo/yahoo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | yahoo 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/demo-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mock-logo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/forms/async-validator/async-validator.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-view-models'; 2 | import DefineMap from 'can-define/map/map'; 3 | import debounce from 'lodash.debounce'; 4 | import View from './async-validator.jsx'; 5 | 6 | export const ViewModel = DefineMap.extend({ 7 | /** 8 | * To prevent the query from running in an endless loop, the params 9 | * are cached here. This allows the query to only run when the params 10 | * have actually changed. 11 | */ 12 | cachedParams: 'any', 13 | updateCachedParams (params) { 14 | this.cachedParams = params; 15 | }, 16 | 17 | /** 18 | * The `params` are passed to the validate as the first argument. 19 | * {Object} 20 | */ 21 | params: { 22 | type: 'any', 23 | value: {} 24 | }, 25 | 26 | /** 27 | * The `otherArgs` will be applied to the query as arguments after the params. 28 | * {Array} 29 | */ 30 | otherArgs: { 31 | type: 'any', 32 | value: [], 33 | set (otherArgs) { 34 | if (!Array.isArray(otherArgs)) { 35 | throw new Error('otherArgs must be an array.'); 36 | } 37 | return otherArgs; 38 | } 39 | }, 40 | 41 | /** 42 | * The query is a debounced version of the validate function with all of the 43 | * passed-in arguments. 44 | */ 45 | runQuery: { 46 | type: 'any', 47 | value () { 48 | return debounce(this.query, this.debounce); 49 | } 50 | }, 51 | 52 | query (params, otherArgs) { 53 | if (!params) { 54 | console.warn('no params were provided to the async form field validator.'); 55 | } 56 | var args = otherArgs.splice(); 57 | args.unshift(params); 58 | return this.validate.apply(undefined, args); 59 | }, 60 | 61 | /** 62 | * The number of milliseconds the query will be debounced. 63 | * Defaults to 250. 64 | * {Integer} 65 | */ 66 | debounce: { 67 | type: 'number', 68 | value: 250 69 | }, 70 | 71 | /** 72 | * The `validate` is the function passed in to performs the async operation. 73 | * It must return a promise. 74 | * {Function} 75 | */ 76 | validate: 'any' 77 | }); 78 | 79 | export default connect(ViewModel, View); 80 | -------------------------------------------------------------------------------- /src/forms/async-validator/async-validator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormField } from 'react-form'; 3 | import isEqual from 'lodash.isequal'; 4 | 5 | export default ({field, query, params, cachedParams, updateCachedParams, otherArgs}) => { 6 | return ( 7 | 8 | {({setValue, getValue}) => { 9 | if (!isEqual(params, cachedParams)) { 10 | setTimeout(() => { 11 | updateCachedParams(params); 12 | query(params, otherArgs).then(response => { 13 | setValue(''); 14 | }) 15 | .catch(error => { 16 | setValue(error.message || error); 17 | }); 18 | }); 19 | } 20 | return ( 21 | 25 | ); 26 | }} 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/forms/form-error/form-error.js: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-view-models'; 2 | import DefineMap from 'can-define/map/map'; 3 | import View from './form-error.jsx'; 4 | 5 | export const ViewModel = DefineMap.extend({ 6 | error: 'string', 7 | clearError: 'any' 8 | }); 9 | 10 | export default connect(ViewModel, View); 11 | -------------------------------------------------------------------------------- /src/forms/form-error/form-error.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({error, clearError}) => { 4 | return ( 5 | error &&
{error}
|| null 6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /src/forms/form/form.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-view-models'; 2 | import DefineMap from 'can-define/map/map'; 3 | import {devWarning} from '../../utils'; 4 | import View from './form.jsx'; 5 | 6 | export const ViewModel = DefineMap.extend({ 7 | '*': { 8 | serialize: true 9 | }, 10 | 11 | /** 12 | * If a strategy attribute is provided, it will be added to the request data. 13 | * This is to make it easy to integrate with feathers-authentication. 14 | */ 15 | strategy: 'string', 16 | 17 | /** 18 | * `Model` is a can-connect compatible Model. Passing a model will create a 19 | * new model instance and save it to the server. 20 | */ 21 | Model: 'any', 22 | 23 | /** 24 | * `service` is a FeathersJS service. Its create method will be used to submit 25 | * data to the server. 26 | */ 27 | service: 'any', 28 | 29 | /** 30 | * There are a few warnings that will show up by default. They can be turned 31 | * off by setting `suppressWarnings` to true. 32 | */ 33 | suppressWarnings: { 34 | value: false 35 | }, 36 | 37 | /** 38 | * When an error is returned from the server, it will end up here. 39 | */ 40 | error: 'string', 41 | 42 | /** 43 | * Clears the error message. 44 | */ 45 | clearError () { 46 | this.error = undefined; 47 | }, 48 | 49 | /** 50 | * If warnings haven't been suppressed, `warn` uses the `devWarning` utility 51 | * to show a console warning message on a development machine. 52 | */ 53 | warn (message) { 54 | if (this.suppressWarnings !== true) { 55 | devWarning(message); 56 | return message; 57 | } 58 | }, 59 | 60 | /** 61 | * `formSubmitted` is the handler for the form. It calls `onSubmit` 62 | * with the auth data. 63 | */ 64 | formSubmitted (values) { 65 | if (this.strategy) { 66 | values.strategy = this.strategy; 67 | } 68 | this.clearError(); 69 | this.onSubmit(values) 70 | .then(response => this.onSuccess(response)) 71 | .catch(error => this.uiError(error)); 72 | }, 73 | 74 | /** 75 | * The default `onSubmit` function uses the Model or service to submit 76 | * data to the server. This function can be overwritten. 77 | */ 78 | onSubmit (data) { 79 | // If a can-connect Model was provided 80 | if (this.Model) { 81 | return new this.Model(data).save(); 82 | // If a Feathers service was provided. 83 | } else if (this.service) { 84 | return this.service.create(data); 85 | // A onSubmit function has to be provided. 86 | } else { 87 | return Promise.reject(new Error(`${this.formName}: You must provide a Model or service attribute, or overwrite the onSubmit function.`)); 88 | } 89 | }, 90 | 91 | /** 92 | * `onSuccess` function gets run when a successful onSubmit response was received. 93 | * In most cases, it will need to be overwritten to handle custom requirements. 94 | */ 95 | onSuccess (data) { 96 | this.warn(`Pass an "onSuccess" function to the ${this.formName} to handle success.`); 97 | }, 98 | 99 | /** 100 | * `uiError` makes sure the UI responds properly to any error received. 101 | * It calls `onError`. 102 | */ 103 | uiError (error) { 104 | this.error = error.message || error; 105 | this.onError(error); 106 | }, 107 | 108 | /** 109 | * When submit fails, the `onError` callback can be used to handle custom 110 | * logic in your app. 111 | */ 112 | onError (error) { 113 | this.warn(error); 114 | } 115 | }); 116 | 117 | export default connect(ViewModel, View); 118 | -------------------------------------------------------------------------------- /src/forms/form/form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'react-form'; 3 | 4 | export default ({ 5 | children, 6 | defaultValues, 7 | loadState, 8 | preValidate, 9 | validate, 10 | onValidationFail, 11 | onChange, 12 | saveState, 13 | willUnmount, 14 | preSubmit, 15 | formSubmitted, 16 | postSubmit, 17 | error, 18 | clearError 19 | }) => { 20 | return ( 21 |
22 |
35 | {children({error, clearError})} 36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/forms/form/form_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | import 'steal-mocha'; 3 | import assert from 'assert'; 4 | import {ViewModel as FormVM} from './form'; 5 | import DefineMap from 'can-define/map/map'; 6 | 7 | describe('Form ViewModel', function () { 8 | it('exports the ViewModel.', function () { 9 | assert(FormVM.prototype instanceof DefineMap); 10 | }); 11 | 12 | describe('well-formed viewModel instance', function () { 13 | it('has all default props in place.', function () { 14 | const vm = new FormVM({}); 15 | const defaults = { 16 | suppressWarnings: false 17 | }; 18 | Object.keys(defaults).map(key => { 19 | assert(vm[key] === defaults[key]); 20 | }); 21 | }); 22 | 23 | it('has all functions in place', function () { 24 | const vm = new FormVM({}); 25 | const functions = [ 26 | 'clearError', 27 | 'warn', 28 | 'formSubmitted', 29 | 'onSubmit', 30 | 'onSuccess', 31 | 'uiError', 32 | 'onError' 33 | ]; 34 | functions.map(fnName => { 35 | assert(typeof vm[fnName] === 'function'); 36 | }); 37 | }); 38 | 39 | it('can have a strategy property', function () { 40 | const vm = new FormVM({ 41 | strategy: 'local' 42 | }); 43 | assert(vm.strategy === 'local'); 44 | }); 45 | 46 | it('can have a Model property', function () { 47 | const vm = new FormVM({ 48 | Model: function () {} 49 | }); 50 | assert(typeof vm.Model === 'function'); 51 | }); 52 | 53 | it('can have a service property', function () { 54 | const vm = new FormVM({ 55 | service: {} 56 | }); 57 | assert(typeof vm.service === 'object'); 58 | }); 59 | }); 60 | 61 | describe('formSubmitted handler', function () { 62 | it('calls the onSubmit function with data', function (done) { 63 | let email = 'marshall@bitovi.com'; 64 | let password = '1234'; 65 | const vm = new FormVM({ 66 | onSubmit (data) { 67 | assert(data.email === email); 68 | assert(data.password === password); 69 | done(); 70 | return Promise.resolve(); 71 | }, 72 | onSuccess () {} 73 | }); 74 | vm.formSubmitted({email, password}); 75 | }); 76 | 77 | it('calls the onSuccess callback with data', function (done) { 78 | let email = 'marshall@bitovi.com'; 79 | let password = '1234'; 80 | const vm = new FormVM({ 81 | username: email, 82 | password: password, 83 | onSubmit (data) { 84 | return Promise.resolve(data); 85 | }, 86 | onSuccess (data) { 87 | assert(data.email === email); 88 | assert(data.password === password); 89 | done(); 90 | } 91 | }); 92 | vm.formSubmitted({email, password}); 93 | }); 94 | 95 | it('calls the uiError function on error', function (done) { 96 | const vm = new FormVM({ 97 | onSubmit () { 98 | let error = new Error('fail bus'); 99 | return Promise.reject(error); 100 | }, 101 | onSuccess (data) { 102 | console.log(data); 103 | }, 104 | uiError (error) { 105 | assert(error); 106 | done(); 107 | } 108 | }); 109 | vm.formSubmitted({}); 110 | }); 111 | 112 | it('calls the onError callback on error', function (done) { 113 | const vm = new FormVM({ 114 | onSubmit () { 115 | let error = new Error('fail bus'); 116 | return Promise.reject(error); 117 | }, 118 | onSuccess (data) { 119 | console.log(data); 120 | }, 121 | onError (error) { 122 | assert(error); 123 | done(); 124 | } 125 | }); 126 | vm.formSubmitted({}); 127 | }); 128 | 129 | it('adds the strategy to data', function (done) { 130 | let email = 'marshall@bitovi.com'; 131 | let password = '1234'; 132 | const vm = new FormVM({ 133 | strategy: 'local', 134 | onSubmit (data) { 135 | assert(data.strategy === 'local'); 136 | return Promise.resolve(); 137 | }, 138 | onSuccess () { 139 | done(); 140 | } 141 | }); 142 | vm.formSubmitted({email, password}); 143 | }); 144 | }); 145 | 146 | describe('Error Messages', function () { 147 | it('can have an error property', function () { 148 | const vm = new FormVM({ 149 | error: 'there was an error' 150 | }); 151 | assert(vm.error === 'there was an error'); 152 | }); 153 | 154 | it('can clear the error message', function () { 155 | const vm = new FormVM({ 156 | error: 'message' 157 | }); 158 | vm.clearError(); 159 | assert(vm.error === undefined); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /src/forms/form/test.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/forms/forms.less: -------------------------------------------------------------------------------- 1 | div.auth-ui-container { 2 | form.auth-component-form { 3 | margin-bottom: 3px; 4 | } 5 | } 6 | 7 | form.auth-component-form { 8 | text-align: center; 9 | font-size: 16px; 10 | margin-top: 22px; 11 | 12 | div.forgot-password { 13 | margin-top: 12px; 14 | height: 15px; 15 | font-size: 12px; 16 | a { 17 | color: #77BFED; 18 | } 19 | } 20 | 21 | button { 22 | width: calc(100% - 10px); 23 | height: 52px; 24 | background: #77BFED; 25 | border: none; 26 | color: white; 27 | margin-top: 22px; 28 | font-size: 16px; 29 | } 30 | 31 | input { 32 | border-bottom: solid 1px #E6E6E6; 33 | border-top: none; 34 | border-left: none; 35 | border-right: none; 36 | height: 30px; 37 | width: calc(100% - 10px); 38 | text-align: center; 39 | padding: 6px 0 2px; 40 | margin-bottom: 4px; 41 | outline: none; 42 | font-size: 14px; 43 | box-sizing: content-box; 44 | 45 | &:focus { 46 | border-bottom: solid 1px #77BFED; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/forms/local-login/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Login Form Demo 4 | 5 | 6 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/forms/local-login/demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import LoginForm from './local-login'; 4 | import DefineMap from 'can-define/map/map'; 5 | 6 | const dummyService = { 7 | create (data) { 8 | return Promise.resolve(data); 9 | } 10 | }; 11 | 12 | const DummyModel = DefineMap.extend({ 13 | id: 'any', 14 | email: 'string', 15 | password: 'string' 16 | }); 17 | DummyModel.prototype.save = function () { 18 | return Promise.resolve(this.serialize()); 19 | }; 20 | 21 | function handleSuccess (loginData) { 22 | loginData.id = 1; 23 | console.log('Login was successful!', loginData); 24 | } 25 | 26 | // Render the DOM 27 | ReactDOM.render( 28 |
29 |
30 |

Login - React Standalone

31 |

Custom field names and validation

32 | { 38 | return Promise.resolve(authData); 39 | }} 40 | validate={({username, secretPhrase}) => { 41 | return { 42 | username: !username ? 'E-mail address is required' : null, 43 | secretPhrase: !secretPhrase ? 'Secret phrase is required' : null 44 | }; 45 | }} /> 46 |
47 | 48 |
49 |

Login - Feathers Service

50 | 51 |
52 | 53 |
54 |

Login - Can-Connect Model

55 | 59 |
60 | 61 |
62 |

Login - Error

63 | { 65 | return new Promise((resolve, reject) => { 66 | setTimeout(() => { 67 | reject('Invalid everything! No soup for you!'); 68 | }, 200); 69 | }); 70 | }} 71 | onSuccess={handleSuccess} 72 | onError={error => { console.error(error); }} /> 73 |
74 |
, 75 | document.querySelector('[root=true]') 76 | ); 77 | -------------------------------------------------------------------------------- /src/forms/local-login/local-login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-form'; 3 | import AuthForm from '../form/form.js'; 4 | import '../forms.less'; 5 | import FormError from '../form-error/form-error'; 6 | 7 | export default ({ 8 | forgotClicked, 9 | usernameField = 'email', 10 | usernamePlaceholder = 'e-mail address', 11 | passwordField = 'password', 12 | passwordPlaceholder = 'password', 13 | buttonText, 14 | ...rest 15 | }) => { 16 | return ( 17 | 18 | {({error, clearError}) => { 19 | return ({values, submitForm}) => { 20 | return ( 21 |
22 | 23 | 24 | 25 | 26 | 27 |
28 | forgot password 29 |
30 | 31 | 32 | 33 | ); 34 | }; 35 | }} 36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/forms/local-signup/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Signup Form Demo 4 | 5 | 6 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/forms/local-signup/demo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import SignupForm from './local-signup'; 4 | import DefineMap from 'can-define/map/map'; 5 | 6 | const dummyService = { 7 | create (data) { 8 | return Promise.resolve(data); 9 | } 10 | }; 11 | 12 | const DummyModel = DefineMap.extend({ 13 | id: 'any', 14 | email: 'string', 15 | password: 'string' 16 | }); 17 | DummyModel.prototype.save = function () { 18 | return Promise.resolve(this.serialize()); 19 | }; 20 | 21 | function handleSuccess (loginData) { 22 | loginData.id = 1; 23 | console.log('Login was successful!', loginData); 24 | } 25 | 26 | function simulatedAsyncValidation (query) { 27 | return new Promise((resolve, reject) => { 28 | setTimeout(() => { 29 | if (query.email === 'contact@bitovi.com') { 30 | reject('That email is unavailable'); 31 | } else { 32 | resolve(true); 33 | } 34 | }, 500); 35 | }); 36 | } 37 | 38 | // Render the DOM 39 | ReactDOM.render( 40 |
41 |
42 |

Signup - React Standalone

43 |

basic validation example

44 | { 50 | return Promise.resolve(authData); 51 | }} 52 | validate={({username, secretPhrase}) => { 53 | return { 54 | username: !username ? 'E-mail address is required' : null, 55 | secretPhrase: !secretPhrase ? 'Secret phrase is required' : null 56 | }; 57 | }} /> 58 |
59 | 60 |
61 |

Signup - Feathers Service

62 | 63 |
64 | 65 |
66 |

Signup - Can-Connect Model

67 |

Async email validation: try contact@bitovi.com

68 | { 71 | return { 72 | email: !email ? 'E-mail address is required' : emailError || null, 73 | password: !password ? 'Password is required' : null 74 | }; 75 | }} 76 | onSuccess={handleSuccess} 77 | usernameField='username' 78 | usernamePlaceholder='username' 79 | asyncValidation={simulatedAsyncValidation} /> 80 |
81 | 82 |
83 |

Signup - Error

84 | { 86 | return new Promise((resolve, reject) => { 87 | setTimeout(() => { 88 | reject('Invalid everything! No soup for you!'); 89 | }, 200); 90 | }); 91 | }} 92 | onSuccess={handleSuccess} 93 | onError={error => { console.error(error); }} /> 94 |
95 |
, 96 | document.querySelector('[root=true]') 97 | ); 98 | -------------------------------------------------------------------------------- /src/forms/local-signup/local-signup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Text } from 'react-form'; 3 | import AsyncValidator from '../async-validator/async-validator'; 4 | import AuthForm from '../form/form.js'; 5 | import '../forms.less'; 6 | import FormError from '../form-error/form-error'; 7 | 8 | export default ({ 9 | asyncValidation, 10 | forgotClicked, 11 | buttonText, 12 | usernameField = 'email', 13 | usernamePlaceholder = 'e-mail address', 14 | passwordField = 'password', 15 | passwordPlaceholder = 'password', 16 | ...rest 17 | }) => { 18 | return ( 19 | 20 | {({error, clearError}) => { 21 | return ({values, submitForm}) => { 22 | let queryParams = { 23 | email: values.email 24 | }; 25 | return ( 26 |
27 | 28 | 29 | 30 | 31 | {asyncValidation && } 32 | 33 |
34 | {/* password strength meter will go here */} 35 |
36 | 37 | 38 | 39 | ); 40 | }; 41 | }} 42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /src/tabs/can-route.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-view-models'; 2 | import DefineMap from 'can-define/map/map'; 3 | import View from './tabs.jsx'; 4 | import route from 'can-route'; 5 | import './tabs.less'; 6 | 7 | export const ViewModel = DefineMap.extend({ 8 | activeTab: { 9 | type: 'string', 10 | set (val) { 11 | if (this.routeAttr) { 12 | route.data[this.routeAttr] = val; 13 | } 14 | return val; 15 | } 16 | }, 17 | 18 | routeAttr: 'string', 19 | 20 | signupClicked () { 21 | this.activeTab = 'signup'; 22 | }, 23 | 24 | loginClicked () { 25 | this.activeTab = 'login'; 26 | } 27 | }); 28 | 29 | export default connect(ViewModel, View); 30 | -------------------------------------------------------------------------------- /src/tabs/tabs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default ({activeTab, signupClicked, loginClicked}) => { 4 | let signupActive = activeTab === 'signup' ? 'active' : ''; 5 | let loginActive = activeTab === 'login' ? 'active' : ''; 6 | 7 | return ( 8 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/tabs/tabs.less: -------------------------------------------------------------------------------- 1 | ul.auth-component-tabs { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0 auto; 5 | display: flex; 6 | font-size: 16px; 7 | font-weight: 400; 8 | 9 | li { 10 | height: 56px; 11 | line-height: 56px; 12 | width: 50%; 13 | text-align: center; 14 | &:not(.active){ 15 | background: #77BFED; 16 | a { 17 | color: white; 18 | display:block; 19 | height: 100%; 20 | width: 100%; 21 | } 22 | } 23 | } 24 | a:active, a:focus, a:hover, a:visited { 25 | text-decoration: none; 26 | } 27 | } -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * `devWarning` is a logging utility that does a console.warn in localhost (dev environment). 3 | */ 4 | export function devWarning (message) { 5 | if (window.location.hostname === 'localhost') { 6 | console.warn(`${message} This message will not show in production.`); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /steal-jsx.js: -------------------------------------------------------------------------------- 1 | export function translate (load) { 2 | load.metadata.format = 'es6'; 3 | } 4 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | import '~/forms/form/form_test'; 2 | --------------------------------------------------------------------------------