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 |
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 | 
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 |
|| 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 |