├── .editorconfig
├── .gitignore
├── .netlify
└── state.json
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── examples
├── example-reach-router
│ ├── .gitignore
│ ├── README.md
│ ├── functions
│ │ ├── async-dadjoke
│ │ │ ├── async-dadjoke.js
│ │ │ └── package.json
│ │ └── authEndPoint
│ │ │ ├── authEndPoint.js
│ │ │ └── package.json
│ ├── index.html
│ ├── package.json
│ ├── public
│ │ ├── _redirects
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ ├── logo.svg
│ │ ├── useLoading.tsx
│ │ └── useLocalState.tsx
│ ├── tsconfig.json
│ └── yarn.lock
└── example-react-router
│ ├── README.md
│ ├── global.d.ts
│ ├── package.json
│ ├── src
│ ├── App.tsx
│ ├── assets
│ │ ├── googleLogo.svg
│ │ └── index.ts
│ ├── components
│ │ ├── AuthOption
│ │ │ ├── AuthOption.styles.ts
│ │ │ └── index.ts
│ │ ├── AuthText
│ │ │ ├── AuthText.styles.ts
│ │ │ └── index.ts
│ │ ├── Button
│ │ │ ├── Button.styles.ts
│ │ │ └── index.ts
│ │ ├── ButtonGoogle
│ │ │ ├── ButtonGoogle.styles.ts
│ │ │ ├── ButtonGoogle.tsx
│ │ │ └── index.ts
│ │ ├── Container
│ │ │ ├── Container.styles.ts
│ │ │ └── index.ts
│ │ ├── Form
│ │ │ ├── Form.styles.ts
│ │ │ └── index.ts
│ │ ├── GlobalStyles
│ │ │ ├── GlobalStyles.styles.ts
│ │ │ └── index.ts
│ │ ├── Header
│ │ │ ├── Header.styles.ts
│ │ │ ├── Header.tsx
│ │ │ └── index.ts
│ │ ├── Input
│ │ │ ├── Input.styles.ts
│ │ │ └── index.ts
│ │ ├── Label
│ │ │ ├── Label.styles.ts
│ │ │ └── index.ts
│ │ ├── TextError
│ │ │ ├── TextError.styles.ts
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── constants
│ │ └── constants.ts
│ ├── index.tsx
│ ├── public
│ │ ├── index.html
│ │ └── robots.txt
│ └── views
│ │ ├── CreateAccount
│ │ ├── CreateAccount.styles.ts
│ │ ├── CreateAccount.tsx
│ │ └── index.ts
│ │ ├── Home
│ │ ├── Home.styles.ts
│ │ ├── Home.tsx
│ │ └── index.ts
│ │ ├── LogIn
│ │ ├── LogIn.tsx
│ │ └── index.ts
│ │ ├── Welcome
│ │ ├── Welcome.styles.ts
│ │ ├── Welcome.tsx
│ │ └── index.ts
│ │ └── index.tsx
│ └── tsconfig.json
├── netlify.toml
├── package.json
├── src
├── index.tsx
├── runRoutes.tsx
└── token.ts
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | .rpt2_cache
11 | .rts2_cache_cjs
12 | .rts2_cache_es
13 | .rts2_cache_umd
14 |
15 | # misc
16 | .DS_Store
17 | .env
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | .netlify
--------------------------------------------------------------------------------
/.netlify/state.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteId": "62390d51-d3e5-4901-a676-8ae1a95ea9dc"
3 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 9
4 | - 8
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
9 |
10 | ## [v0.2.7](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.6...v0.2.7) - 2022-06-15
11 |
12 | ### Merged
13 |
14 | - fix: package.module again [`#50`](https://github.com/netlify-labs/react-netlify-identity/pull/50)
15 |
16 | ## [v0.2.6](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.5...v0.2.6) - 2022-06-14
17 |
18 | ### Merged
19 |
20 | - fix: typo in `module` field [`#49`](https://github.com/netlify-labs/react-netlify-identity/pull/49)
21 |
22 | ### Fixed
23 |
24 | - fix: typo in `module` field [`#48`](https://github.com/netlify-labs/react-netlify-identity/issues/48)
25 |
26 | ### Commits
27 |
28 | - Update README.md [`8799796`](https://github.com/netlify-labs/react-netlify-identity/commit/879979601df6dba0ca95badb2a0988613565a5a8)
29 |
30 | ## [v0.2.5](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.4...v0.2.5) - 2020-04-20
31 |
32 | ### Merged
33 |
34 | - make tsdx a dev dependency [`#36`](https://github.com/netlify-labs/react-netlify-identity/pull/36)
35 |
36 | ## [v0.2.4](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.3...v0.2.4) - 2020-04-10
37 |
38 | ### Merged
39 |
40 | - Store user after email confirmation [`#35`](https://github.com/netlify-labs/react-netlify-identity/pull/35)
41 |
42 | ### Commits
43 |
44 | - feat(runRoutes): store user after email confirmation [`929c477`](https://github.com/netlify-labs/react-netlify-identity/commit/929c4771c2caa4b64d4232be0e37bf4221d2f27f)
45 |
46 | ## [v0.2.3](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.2...v0.2.3) - 2020-02-04
47 |
48 | ### Commits
49 |
50 | - fix accidental break in ReactNetlifyIdentityAPI.signupUser type signature [`182faf9`](https://github.com/netlify-labs/react-netlify-identity/commit/182faf90bde92c260134965d511690dceeed3265)
51 |
52 | ## [v0.2.2](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.1...v0.2.2) - 2020-01-18
53 |
54 | ### Merged
55 |
56 | - V0.2.2 [`#32`](https://github.com/netlify-labs/react-netlify-identity/pull/32)
57 |
58 | ### Commits
59 |
60 | - feat(verifyToken): add & expose verifyToken reject instead of error [`b4caa6f`](https://github.com/netlify-labs/react-netlify-identity/commit/b4caa6fe87f4f57efd1d85868786b321d51887b3)
61 | - add @see to all gotrue-js methods [`94d1698`](https://github.com/netlify-labs/react-netlify-identity/commit/94d1698488e6ea9975750191cb41386661fcc36f)
62 | - fix(hash): remove entirely [`0033b60`](https://github.com/netlify-labs/react-netlify-identity/commit/0033b604f441e54b65037dfac8c733c9918eee42)
63 |
64 | ## [v0.2.1](https://github.com/netlify-labs/react-netlify-identity/compare/v0.2.0...v0.2.1) - 2020-01-16
65 |
66 | ### Merged
67 |
68 | - v0.2.1 [`#31`](https://github.com/netlify-labs/react-netlify-identity/pull/31)
69 |
70 | ### Commits
71 |
72 | - fix(readme): update to latest changes [`abdcdad`](https://github.com/netlify-labs/react-netlify-identity/commit/abdcdada960849d7059affcfeaee35c9b8036d0f)
73 | - fix(recoverAccount): also clean up potentially rejected token [`226aea3`](https://github.com/netlify-labs/react-netlify-identity/commit/226aea39809ba08ff282acf74ece24c518e8b2a4)
74 | - fix(readme): finish docs [`8e30593`](https://github.com/netlify-labs/react-netlify-identity/commit/8e305934d6a3801cd556ea1edd6b0fc1f7316e13)
75 |
76 | ## [v0.2.0](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.9...v0.2.0) - 2020-01-15
77 |
78 | ### Merged
79 |
80 | - recoverAccount workflow... and a bit more [`#30`](https://github.com/netlify-labs/react-netlify-identity/pull/30)
81 | - Bump safer-eval from 1.3.3 to 1.3.5 in /examples/example-reach-ro… [`#23`](https://github.com/netlify-labs/react-netlify-identity/pull/23)
82 | - Bump mixin-deep from 1.3.1 to 1.3.2 [`#19`](https://github.com/netlify-labs/react-netlify-identity/pull/19)
83 | - Bump mixin-deep from 1.3.1 to 1.3.2 in /examples/example-reach-ro… [`#20`](https://github.com/netlify-labs/react-netlify-identity/pull/20)
84 | - Bump lodash from 4.17.11 to 4.17.15 [`#21`](https://github.com/netlify-labs/react-netlify-identity/pull/21)
85 | - Bump lodash from 4.17.11 to 4.17.15 in /examples/example-reach-ro… [`#22`](https://github.com/netlify-labs/react-netlify-identity/pull/22)
86 | - Added example [`#18`](https://github.com/netlify-labs/react-netlify-identity/pull/18)
87 |
88 | ### Commits
89 |
90 | - chore(deps): bumped versions of all dependencies [`2e5cdba`](https://github.com/netlify-labs/react-netlify-identity/commit/2e5cdbaf7258a4405c61fe96e532670df1a09008)
91 | - Add example [`18bf4ba`](https://github.com/netlify-labs/react-netlify-identity/commit/18bf4ba32097464ac10c16f5096a696bf965193a)
92 | - fix(index): group state & effects, memoize functions [`3cd647c`](https://github.com/netlify-labs/react-netlify-identity/commit/3cd647c2d6a25a695f696a166c29034bd40e03e1)
93 |
94 | ## [v0.1.9](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.8...v0.1.9) - 2019-07-02
95 |
96 | ### Merged
97 |
98 | - Change parameter type in updateUser method [`#15`](https://github.com/netlify-labs/react-netlify-identity/pull/15)
99 |
100 | ### Commits
101 |
102 | - gs [`edcdeb9`](https://github.com/netlify-labs/react-netlify-identity/commit/edcdeb905d4f17398e90effdecbbe8a9f90715ef)
103 | - redeploy [`6756cf1`](https://github.com/netlify-labs/react-netlify-identity/commit/6756cf1daeafdfd963e8f927ea1db6df2831c14b)
104 |
105 | ## [v0.1.8](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.7...v0.1.8) - 2019-06-23
106 |
107 | ### Merged
108 |
109 | - Add flag enableRunRoutes [`#14`](https://github.com/netlify-labs/react-netlify-identity/pull/14)
110 |
111 | ### Commits
112 |
113 | - move down the usememo [`563b9de`](https://github.com/netlify-labs/react-netlify-identity/commit/563b9de634e65eff1635b657cc96d28a41456d94)
114 | - make sure we use RNI 0.1.7 [`11b78d1`](https://github.com/netlify-labs/react-netlify-identity/commit/11b78d11257503f69ac10b36ac96e1661f36b2ae)
115 | - tweak readme [`e0cf22c`](https://github.com/netlify-labs/react-netlify-identity/commit/e0cf22c2d76de0b935716b1ec16151c96b05e45e)
116 |
117 | ## [v0.1.7](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.6...v0.1.7) - 2019-06-10
118 |
119 | ### Commits
120 |
121 | - memoize the used hook [`e92f779`](https://github.com/netlify-labs/react-netlify-identity/commit/e92f779c363e561f6c29b6bfdc8a9ed2e871859e)
122 | - use profiling build of react dom [`e05047c`](https://github.com/netlify-labs/react-netlify-identity/commit/e05047ccb959ade2b54fa4880779e7bd2a2dea6d)
123 |
124 | ## [v0.1.6](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.5...v0.1.6) - 2019-06-10
125 |
126 | ### Commits
127 |
128 | - memoize gotrueinstance creation [`aea606b`](https://github.com/netlify-labs/react-netlify-identity/commit/aea606b6e6dbea34d4c0350ad5dff2ce07db1c45)
129 |
130 | ## [v0.1.5](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.4...v0.1.5) - 2019-06-10
131 |
132 | ### Commits
133 |
134 | - switch example to always use latest from npm [`a1cee30`](https://github.com/netlify-labs/react-netlify-identity/commit/a1cee30ae0fee27437af89a6e5179c81eca61a90)
135 | - alright drop all dependencies [`8c3c6fa`](https://github.com/netlify-labs/react-netlify-identity/commit/8c3c6fa5ccfbf84fbc04b2dadaef540860b00c6a)
136 | - skip prepare script [`a8e8a79`](https://github.com/netlify-labs/react-netlify-identity/commit/a8e8a79c9ce09891f2f6f46bbaa0988f88e31b6a)
137 |
138 | ## [v0.1.4](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.3...v0.1.4) - 2019-06-10
139 |
140 | ### Commits
141 |
142 | - rename api [`2abd831`](https://github.com/netlify-labs/react-netlify-identity/commit/2abd831b5139dacde926152839ee966db76586bc)
143 | - fix useeffect infinite loop [`4af4061`](https://github.com/netlify-labs/react-netlify-identity/commit/4af40618db02631437d9618b7c17e466cc8c732f)
144 |
145 | ## [v0.1.3](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.2...v0.1.3) - 2019-05-30
146 |
147 | ### Commits
148 |
149 | - fix readme [`16f3a04`](https://github.com/netlify-labs/react-netlify-identity/commit/16f3a044ceea85a91c7e22f31f3ada631e30fb2e)
150 | - rename to useIdentityContext [`ff50d61`](https://github.com/netlify-labs/react-netlify-identity/commit/ff50d61014a8a5ff70fd74477b61d4a11701d3ea)
151 |
152 | ## [v0.1.2](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.1...v0.1.2) - 2019-05-30
153 |
154 | ### Commits
155 |
156 | - fix exampple [`6c4d722`](https://github.com/netlify-labs/react-netlify-identity/commit/6c4d7229a1672557a12147adc8dc59f027e544ac)
157 |
158 | ## [v0.1.1](https://github.com/netlify-labs/react-netlify-identity/compare/v0.1.0...v0.1.1) - 2019-05-30
159 |
160 | ### Merged
161 |
162 | - Use context api [`#9`](https://github.com/netlify-labs/react-netlify-identity/pull/9)
163 |
164 | ### Commits
165 |
166 | - change settings api and remove useMemo call [`77099a3`](https://github.com/netlify-labs/react-netlify-identity/commit/77099a307aba8525da8689d5f4a7175f27e619db)
167 | - fix build [`964b3ae`](https://github.com/netlify-labs/react-netlify-identity/commit/964b3ae401a7cffa3a2b5eeb837a4ab6aec6343a)
168 |
169 | ## [v0.1.0](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.16...v0.1.0) - 2019-05-30
170 |
171 | ### Commits
172 |
173 | - working contextapi [`149fccf`](https://github.com/netlify-labs/react-netlify-identity/commit/149fccf91317ffade6b5468c19cdfcc333cf245d)
174 | - update version [`013e1fd`](https://github.com/netlify-labs/react-netlify-identity/commit/013e1fdf8fe2a74c5844af5b87221d224e51f3be)
175 |
176 | ## [v0.0.16](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.15...v0.0.16) - 2019-05-22
177 |
178 | ### Commits
179 |
180 | - example [`55dbe28`](https://github.com/netlify-labs/react-netlify-identity/commit/55dbe287bdd10f288489fac9820abf3cbdd06c52)
181 | - move into function folders [`43dfa8b`](https://github.com/netlify-labs/react-netlify-identity/commit/43dfa8b81c80db0f292c71fb65f685b9a0d7594e)
182 | - readme [`1521c5b`](https://github.com/netlify-labs/react-netlify-identity/commit/1521c5b1b9843ce1cc1c82aec9d9c2e818599f38)
183 |
184 | ## [v0.0.15](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.14...v0.0.15) - 2019-04-28
185 |
186 | ### Commits
187 |
188 | - extract to runRoutes [`3b88029`](https://github.com/netlify-labs/react-netlify-identity/commit/3b88029ac98ea6874135569f21f4605a2263564d)
189 | - app buttons [`ef6b9c1`](https://github.com/netlify-labs/react-netlify-identity/commit/ef6b9c16ad029498935d9d4e24428d2212b5a1b6)
190 | - pkgjson [`50ae246`](https://github.com/netlify-labs/react-netlify-identity/commit/50ae2462e80195234b2813a24e946deda996d20d)
191 |
192 | ## [v0.0.14](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.13...v0.0.14) - 2019-04-28
193 |
194 | ### Commits
195 |
196 | - tryloginprovider [`b6523ff`](https://github.com/netlify-labs/react-netlify-identity/commit/b6523ff6cac9d1d09e549bc21055d32a1e6a1f29)
197 | - bump version [`bee1f88`](https://github.com/netlify-labs/react-netlify-identity/commit/bee1f888037ab22265874b7398e2dc051b9eb3b1)
198 |
199 | ## [v0.0.13](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.12...v0.0.13) - 2019-04-28
200 |
201 | ### Commits
202 |
203 | - try to deploy [`4dedb1d`](https://github.com/netlify-labs/react-netlify-identity/commit/4dedb1d74115413946cdeda613f29b9c7c76bbb6)
204 | - pkgjson [`2d15208`](https://github.com/netlify-labs/react-netlify-identity/commit/2d1520885fdc7c17eeef9587ce01f5964678dbe9)
205 | - readme [`743a354`](https://github.com/netlify-labs/react-netlify-identity/commit/743a3548b2a3b0f927b60bb76928b96b4db2c022)
206 |
207 | ## [v0.0.12](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.11...v0.0.12) - 2019-04-24
208 |
209 | ### Commits
210 |
211 | - actually make it remember [`a8a8106`](https://github.com/netlify-labs/react-netlify-identity/commit/a8a8106c9317228848c349487b8626040bc5ebf0)
212 |
213 | ## [v0.0.11](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.10...v0.0.11) - 2019-04-23
214 |
215 | ### Commits
216 |
217 | - bump dependencies [`cfb29a6`](https://github.com/netlify-labs/react-netlify-identity/commit/cfb29a6af608a3bdae412ce43d73fa5a54db7e86)
218 | - remove example, misleading [`2761f6a`](https://github.com/netlify-labs/react-netlify-identity/commit/2761f6a931889d1b23fe0ad10e157e8b08c6d2e0)
219 |
220 | ## [v0.0.10](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.9...v0.0.10) - 2019-04-23
221 |
222 | ### Commits
223 |
224 | - bump gotruejs [`0a88980`](https://github.com/netlify-labs/react-netlify-identity/commit/0a88980df1b347565b0f9e772288e66a03e6eea1)
225 |
226 | ## [v0.0.9](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.8...v0.0.9) - 2019-04-22
227 |
228 | ### Commits
229 |
230 | - proper dependencies [`20fb43a`](https://github.com/netlify-labs/react-netlify-identity/commit/20fb43a75dde9e81afef7bf6c008c5ce5fa5c8c3)
231 |
232 | ## [v0.0.8](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.7...v0.0.8) - 2019-04-22
233 |
234 | ### Merged
235 |
236 | - Add remember option to loginUser [`#2`](https://github.com/netlify-labs/react-netlify-identity/pull/2)
237 |
238 | ### Commits
239 |
240 | - Add @svgr/rollup devDependency to package.json [`0ecb730`](https://github.com/netlify-labs/react-netlify-identity/commit/0ecb730a6601d477ed6018f99aa8a2b840a6fa1c)
241 | - user [`0a99857`](https://github.com/netlify-labs/react-netlify-identity/commit/0a998577cca36567c0d5bf8a280b600b6894d9b8)
242 | - 3rd arg loginuser [`6856fad`](https://github.com/netlify-labs/react-netlify-identity/commit/6856fad19c87bb989771a218ac6d061052a335bb)
243 |
244 | ## [v0.0.7](https://github.com/netlify-labs/react-netlify-identity/compare/0.0.7...v0.0.7) - 2019-03-15
245 |
246 | ### Commits
247 |
248 | - pull currentuser on init [`3b593dd`](https://github.com/netlify-labs/react-netlify-identity/commit/3b593dded394462ca2679fcfc57baa31d450b2e2)
249 |
250 | ## [0.0.7](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.5...0.0.7) - 2019-01-10
251 |
252 | ### Commits
253 |
254 | - dont use ref [`dc1c0f0`](https://github.com/netlify-labs/react-netlify-identity/commit/dc1c0f093cf07df182444e1e4962bebcfbf2e0e2)
255 | - noproptypes [`72e75ba`](https://github.com/netlify-labs/react-netlify-identity/commit/72e75bacdd055fee1ef7b1dcf2440650be13c2e5)
256 |
257 | ## [v0.0.5](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.3...v0.0.5) - 2019-01-10
258 |
259 | ### Commits
260 |
261 | - update certain things [`561db87`](https://github.com/netlify-labs/react-netlify-identity/commit/561db8766f75d130dc64ca3f52e0a313b7dd3119)
262 | - Update README.md [`bc39e80`](https://github.com/netlify-labs/react-netlify-identity/commit/bc39e80f587bd6fbeddd9d6e2c52262ef5b2992b)
263 |
264 | ## [v0.0.3](https://github.com/netlify-labs/react-netlify-identity/compare/v0.0.2...v0.0.3) - 2018-12-10
265 |
266 | ### Commits
267 |
268 | - readme [`a20d173`](https://github.com/netlify-labs/react-netlify-identity/commit/a20d173ab4a7f00d905ee9a68091913924da1e91)
269 | - update readme [`61124da`](https://github.com/netlify-labs/react-netlify-identity/commit/61124da98761c5d765d5ddc1c0ecbbcd957e9c4b)
270 |
271 | ## v0.0.2 - 2018-12-10
272 |
273 | ### Commits
274 |
275 | - init create-react-library@2.6.7 [`2ae1fc1`](https://github.com/netlify-labs/react-netlify-identity/commit/2ae1fc1b5bd3d868a05bbea02c8611699714abf8)
276 | - init [`acdf2e2`](https://github.com/netlify-labs/react-netlify-identity/commit/acdf2e29850e6c6a18ac1c33ae3aa373cd878e9e)
277 | - init [`71ba046`](https://github.com/netlify-labs/react-netlify-identity/commit/71ba046d3320807cb411dd270973cd9481bd7486)
278 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SEEKING MAINTAINERS
2 |
3 | i've [left Netlify](https://www.swyx.io/writing/farewell-netlify/). obviously i still care about netlify but im gonna have other stuff to do and wont be able to give this my full attention. pls open an issue if you use this and want to take over. post your npm username too. i'd love to find a good home for this project.
4 |
5 | ---
6 |
7 | # react-netlify-identity
8 |
9 | > [Netlify Identity](https://www.netlify.com/docs/identity/?utm_source=github&utm_medium=swyx-RNI&utm_campaign=devex) + React Hooks, with Typescript. Bring your own UI!
10 |
11 | [](https://www.npmjs.com/package/react-netlify-identity) [](https://standardjs.com)
12 |
13 | Use [Netlify Identity](https://www.netlify.com/docs/identity/?utm_source=github&utm_medium=swyx-RNI&utm_campaign=devex) easier with React! This is a thin wrapper over the [gotrue-js](https://github.com/netlify/gotrue-js) library for easily accessing Netlify Identity functionality in your app, with React Context and Hooks. Types are provided.
14 |
15 | Three demos:
16 |
17 | - [a full demo here](https://netlify-gotrue-in-react.netlify.com/) with [source code](https://github.com/netlify/create-react-app-lambda/tree/reachRouterAndGoTrueDemo/src)
18 | - [example with Reach Router](https://github.com/sw-yx/react-netlify-identity/tree/master/examples/example-reach-router) with [a demo hosted here](https://react-netlify-identity.netlify.com) and [deployed logs here](https://app.netlify.com/sites/react-netlify-identity/deploys)
19 | - [example with React Router](https://github.com/sw-yx/react-netlify-identity/tree/master/examples/example-react-router) with [a demo hosted here](https://react-netlify-identity-example.netlify.com)
20 |
21 | **This library is not officially maintained by Netlify.** This is written by swyx for his own use (and others with like minds 😎) and will be maintained as a personal project unless formally adopted by Netlify. See below for official alternatives.
22 |
23 | ## Blogposts
24 |
25 | - [Add Netlify Identity Authentication to any React App in 5 minutes with React Context, Hooks and Suspense](https://dev.to/swyx/add-netlify-identity-authentication-to-any-react-app-in-5-minutes-with-react-context-hooks-and-suspense-5gci)
26 |
27 | ## List of Alternatives
28 |
29 | **Lowest level JS Library**: If you want to use the official Javascript bindings to GoTrue, Netlify's underlying Identity service written in Go, use https://github.com/netlify/gotrue-js
30 |
31 | **React bindings**: If you want a thin wrapper over Gotrue-js for React, `react-netlify-identity` is a "headless" library, meaning there is no UI exported and you will write your own UI to work with the authentication. https://github.com/sw-yx/react-netlify-identity. If you want a drop-in UI, there is yet another library that wraps `react-netlify-identity`: https://github.com/sw-yx/react-netlify-identity-widget
32 |
33 | **High level overlay**: If you want a "widget" overlay that gives you a nice UI out of the box, with a somewhat larger bundle, check https://github.com/netlify/netlify-identity-widget
34 |
35 | **High level popup**: If you want a popup window approach also with a nice UI out of the box, and don't mind the popup flow, check https://github.com/netlify/netlify-auth-providers
36 |
37 | ## Typescript
38 |
39 | Library is written in Typescript. File an issue if you find any problems.
40 |
41 | ## Install
42 |
43 | ```bash
44 | yarn add react-netlify-identity
45 | ```
46 |
47 | ## Usage
48 |
49 | ⚠️ **Important:** You will need to have an active Netlify site running with Netlify Identity turned on. [Click here for instructions](https://www.netlify.com/docs/identity/#getting-started?utm_source=github&utm_medium=swyx-RNI&utm_campaign=devex) to get started/double check that it is on. We will need your site's url (e.g. `https://mysite.netlify.com`) to initialize `IdentityContextProvider`.
50 |
51 | **When you call useIdentityContext()**, you can destructure these variables and methods:
52 |
53 | - `user: User`
54 | - `setUser`: directly set the user object. Not advised; use carefully!! mostly you should use the methods below
55 | - `isConfirmedUser: boolean`: if they have confirmed their email
56 | - `isLoggedIn: boolean`: if the user is logged in
57 | - `signupUser(email: string, password: string, data: Object, directLogin: boolean = true)`
58 | - `directLogin` will internally set state to the newly created user
59 | - `isConfirmedUser` will be false
60 | - `isLoggedIn` will be true
61 | - setting `directLogin` to false won't trigger the state
62 | - `loginUser(email: string, password: string, remember: boolean = true)` - we default the `remember` term to `true` since you'll usually want to remember the session in localStorage. set it to false if you need to
63 | - `logoutUser()`
64 | - `requestPasswordRecovery(email: string)`
65 | - `updateUser(fields: object)`: see [updateUser @ gotrue-js](https://github.com/netlify/gotrue-js#update-a-user)
66 | - `getFreshJWT()`
67 | - `authedFetch(endpoint: string, obj: RequestInit = {})` a thin axios-like wrapper over `fetch` that has the user's JWT attached, for convenience pinging Netlify Functions with Netlify Identity
68 | - `recoverAccount(remember?: boolean)`: verifies and consumes the recovery token caught by `runRoutes`, sets user on success
69 | - `param: TokenParam`
70 | - a token exposing Netlify tokens a dev has to implement the actions for; namely `invite`, `recovery`, `email_change` and `access_denied`
71 | - **important:** tokens this package exposes no methods for are automatically handled and will not be passed down - see [runRoutes implementation](https://github.com/sw-yx/react-netlify-identity/master/src/runRoutes.tsx)
72 | - if you don't want this behaviour (added [here](https://github.com/sw-yx/react-netlify-identity/issues/12) in v.0.1.8), pass `runRoutes={false}` to the exposed hook
73 | - for further reference, please check the [type definition](https://github.com/sw-yx/react-netlify-identity/tree/master/src/token.ts)
74 | - an example implementation for a Recovery process can be found below
75 | - `verifyToken()`
76 | - consumes & verifies TokenParam based on the type and tries to retrieve a valid user object
77 | - devs duty to show password field to afterwards call `signupUser(user.email, newPassword)`
78 |
79 | ```tsx
80 | import React from 'react';
81 |
82 | import { IdentityContextProvider } from 'react-netlify-identity';
83 |
84 | function App() {
85 | const url = 'https://your-identity-instance.netlify.com/'; // supply the url of your Netlify site instance. VERY IMPORTANT. no point putting in env var since this is public anyway
86 | return (
87 |
88 | {/* rest of your app */}
89 |
90 | );
91 | }
92 | ```
93 |
94 |
95 |
96 |
97 | Click for More Example code
98 |
99 |
100 |
101 | ```tsx
102 | import { useIdentityContext } from 'react-netlify-identity';
103 |
104 | // log in/sign up example
105 | function Login() {
106 | const { loginUser, signupUser } = useIdentityContext();
107 | const formRef = React.useRef();
108 | const [msg, setMsg] = React.useState('');
109 |
110 | const signup = () => {
111 | const email = formRef.current.email.value;
112 | const password = formRef.current.password.value;
113 |
114 | signupUser(email, password)
115 | .then(user => {
116 | console.log('Success! Signed up', user);
117 | navigate('/dashboard');
118 | })
119 | .catch(err => console.error(err) || setMsg('Error: ' + err.message));
120 | };
121 |
122 | return (
123 |
155 | );
156 | }
157 |
158 | // log out user
159 | function Logout() {
160 | const { logoutUser } = useIdentityContext();
161 | return You are signed in. Log Out ;
162 | }
163 |
164 | // check `identity.user` in a protected route
165 | function PrivateRoute(props) {
166 | const identity = useIdentityContext();
167 | let { as: Comp, ...rest } = props;
168 | return identity.user ? (
169 |
170 | ) : (
171 |
172 |
You are trying to view a protected page. Please log in
173 |
174 |
175 | );
176 | }
177 |
178 | // check if user has confirmed their email
179 | // use authedFetch API to make a request to Netlify Function with the user's JWT token,
180 | // letting your function use the `user` object
181 | function Dashboard() {
182 | const { isConfirmedUser, authedFetch } = useIdentityContext();
183 | const [msg, setMsg] = React.useState('Click to load something');
184 | const handler = () => {
185 | authedFetch.get('/.netlify/functions/authEndPoint').then(setMsg);
186 | };
187 | return (
188 |
189 |
This is a Protected Dashboard!
190 | {!isConfirmedUser && (
191 |
192 | You have not confirmed your email. Please confirm it before you ping
193 | the API.
194 |
195 | )}
196 |
197 |
198 |
You can try pinging our authenticated API here.
199 |
200 | If you are logged in, you should be able to see a `user` info here.
201 |
202 |
Ping authenticated API
203 |
{JSON.stringify(msg, null, 2)}
204 |
205 |
206 | );
207 | }
208 | ```
209 |
210 |
211 |
212 |
213 |
214 |
215 | How to handle a Recovery Action (since v0.2)
216 |
217 |
218 |
219 | Of course you can alternatively inline this logic into app.
220 |
221 | ```tsx
222 | import { useIdentityContext } from 'react-netlify-identity';
223 | import {
224 | BrowserRouter as Router,
225 | Switch,
226 | Route,
227 | useLocation,
228 | useHistory,
229 | } from 'react-router-dom';
230 |
231 | export default function App() {
232 | const { isLoggedIn } = useIdentityContext();
233 |
234 | return (
235 |
236 |
237 |
238 | {isLoggedIn ? (
239 | <>
240 |
241 | } />
242 | >
243 | ) : (
244 | <>
245 |
246 |
247 |
248 | {/* etc */}
249 |
250 | } />
251 | >
252 | )}
253 |
254 |
255 | );
256 | }
257 |
258 | function CatchNetlifyRecoveryNullComponent() {
259 | const {
260 | param: { token, type },
261 | } = useIdentityContext();
262 | const { replace } = useHistory();
263 | const { pathname } = useLocation();
264 |
265 | // important to check for the current pathname here because else you land
266 | // in a infinite loop
267 | if (token && type === 'recovery' && pathname === '/') {
268 | replace(`/recovery`, { token });
269 | }
270 |
271 | return null;
272 | }
273 |
274 | function RecoveryPage() {
275 | const {
276 | location: { state },
277 | } = useHistory();
278 | // this state _might_ not be needed, it was needed in my specific implementation
279 | const [token] = useState(state?.token);
280 |
281 | return null; // set new password in a form and call updateUser
282 | }
283 | ```
284 |
285 |
286 |
287 | ## Lower level API: `useNetlifyIdentity`
288 |
289 | If you'd like to handle your own context yourself, you can use this library as a hook as well:
290 |
291 | ```tsx
292 | function useNetlifyIdentity(
293 | url: string,
294 | onAuthChange: authChangeParam = () => {},
295 | enableRunRoutes: boolean = true
296 | ): ReactNetlifyIdentityAPI;
297 | ```
298 |
299 | ## License
300 |
301 | MIT © [sw-yx](https://github.com/sw-yx)
302 |
--------------------------------------------------------------------------------
/examples/example-reach-router/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .cache
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/example-reach-router/README.md:
--------------------------------------------------------------------------------
1 | #Netlify Identity + Reach Router by wrapping the goTrue API in a React Hook.
2 |
3 | [Deployed site here](https://unruffled-roentgen-04c3b8.netlify.com/)
4 |
5 | this is a demo of using Netlify Identity with Reach Router by wrapping the goTrue API in a React Hook.
6 |
7 | ⚠️Make sure Netlify Identity is enabled!!! or demo wont work
8 |
9 | Reach Router is used to show authentication as per [Ryan Florence](https://twitter.com/ryanflorence/status/1060361144701833216).
10 |
11 | If you want to fork/deploy this yourself on Netlfiy, click this:
12 |
13 | [](https://app.netlify.com/start/deploy?repository=https://github.com/netlify/create-react-app-lambda/tree/reachRouterAndGoTrueDemo&stack=cms)
14 |
--------------------------------------------------------------------------------
/examples/example-reach-router/functions/async-dadjoke/async-dadjoke.js:
--------------------------------------------------------------------------------
1 | // example of async handler using async-await
2 | // https://github.com/netlify/netlify-lambda/issues/43#issuecomment-444618311
3 |
4 | const fetch = require("node-fetch")
5 | exports.handler = async function handler(event, context) {
6 | try {
7 | const response = await fetch("https://icanhazdadjoke.com", { headers: { Accept: "application/json" } })
8 | if (!response.ok) {
9 | // NOT res.status >= 200 && res.status < 300
10 | return { statusCode: response.status, body: response.statusText }
11 | }
12 | const data = await response.json()
13 |
14 | return {
15 | statusCode: 200,
16 | body: JSON.stringify({ msg: data.joke })
17 | }
18 | } catch (err) {
19 | console.log(err) // output to netlify function log
20 | return {
21 | statusCode: 500,
22 | body: JSON.stringify({ msg: err.message }) // Could be a custom message or object i.e. JSON.stringify(err)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/example-reach-router/functions/async-dadjoke/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambda",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "async-dadjoke.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "node-fetch": "^2.4.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-reach-router/functions/authEndPoint/authEndPoint.js:
--------------------------------------------------------------------------------
1 | // example of async handler using async-await
2 | // https://github.com/netlify/netlify-lambda/issues/43#issuecomment-444618311
3 |
4 | const fetch = require("node-fetch")
5 | exports.handler = async function handler(event, context) {
6 | if (!context.clientContext && !context.clientContext.identity) {
7 | return {
8 | statusCode: 500,
9 | body: JSON.stringify({
10 | msg:
11 | "No identity instance detected. Did you enable it? Also, Netlify Identity is not supported on local dev yet."
12 | }) // Could be a custom message or object i.e. JSON.stringify(err)
13 | }
14 | }
15 | const { identity, user } = context.clientContext
16 | try {
17 | const response = await fetch("https://api.chucknorris.io/jokes/random")
18 | if (!response.ok) {
19 | // NOT res.status >= 200 && res.status < 300
20 | return { statusCode: response.status, body: response.statusText }
21 | }
22 | const data = await response.json()
23 |
24 | return {
25 | statusCode: 200,
26 | body: JSON.stringify({ identity, user, msg: data.value })
27 | }
28 | } catch (err) {
29 | console.log(err) // output to netlify function log
30 | return {
31 | statusCode: 500,
32 | body: JSON.stringify({ msg: err.message }) // Could be a custom message or object i.e. JSON.stringify(err)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/example-reach-router/functions/authEndPoint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambda",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "async-dadjoke.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "node-fetch": "^2.4.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-reach-router/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Playground
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/example-reach-router/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "@reach/router": "^1.2.1",
12 | "react": "^15.0.0 || ^16.0.0",
13 | "react-app-polyfill": "^1.0.1",
14 | "react-dom": "^15.0.0 || ^16.0.0",
15 | "react-netlify-identity": "^0.1.8"
16 | },
17 | "alias": {
18 | "react": "../node_modules/react",
19 | "react-dom": "../node_modules/react-dom/profiling",
20 | "react-dom$": "../node_modules/react-dom/profiling",
21 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
22 | },
23 | "devDependencies": {
24 | "@types/reach__router": "^1.2.4",
25 | "@types/react": "^16.8.15",
26 | "@types/react-dom": "^16.8.4",
27 | "parcel": "^1.12.3",
28 | "typescript": "^3.4.5"
29 | },
30 | "browserslist": [
31 | "last 1 chrome version",
32 | "not dead"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/examples/example-reach-router/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/examples/example-reach-router/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netlify-labs/react-netlify-identity/9b879404194300851cf7652fd8544747dd01291e/examples/example-reach-router/public/favicon.ico
--------------------------------------------------------------------------------
/examples/example-reach-router/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 | You need to enable JavaScript to run this app.
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/examples/example-reach-router/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .title {
6 | color: #d44b15;
7 | text-align: center;
8 | background-color: #e5e4e4;
9 | font-family: Lato, serif;
10 | margin-top: 0px;
11 | display: flex;
12 |
13 | align-items: center;
14 | justify-content: center;
15 | }
16 |
17 | .title .italic {
18 | color: #fff;
19 | font-size: 2em;
20 | font-style: italic;
21 | vertical-align: bottom;
22 | margin: 0 -0.3em;
23 | }
24 | pre {
25 | text-align: left;
26 | overflow-x: scroll;
27 | overflow-y: hidden;
28 | }
29 |
30 | form {
31 | text-align: left;
32 | margin: 0 auto;
33 | display: inline-block;
34 | }
35 | form > div {
36 | margin-bottom: 1rem;
37 | }
38 |
39 | nav {
40 | background: #ffe259; /* fallback for old browsers */
41 | background: -webkit-linear-gradient(
42 | to right,
43 | #ffa751,
44 | #ffe259
45 | ); /* Chrome 10-25, Safari 5.1-6 */
46 | background: linear-gradient(
47 | to right,
48 | #ffa751,
49 | #ffe259
50 | ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
51 | padding: 1rem;
52 | font-family: Lato, serif;
53 | font-weight: bold;
54 | margin-bottom: 1rem;
55 | }
56 |
57 | nav a {
58 | color: brown;
59 | text-decoration: none;
60 | }
61 |
62 | /* http://tobiasahlin.com/spinkit/ */
63 |
64 | .sk-folding-cube {
65 | margin: 20px auto;
66 | width: 40px;
67 | height: 40px;
68 | position: relative;
69 | -webkit-transform: rotateZ(45deg);
70 | transform: rotateZ(45deg);
71 | }
72 |
73 | .sk-folding-cube .sk-cube {
74 | float: left;
75 | width: 50%;
76 | height: 50%;
77 | position: relative;
78 | -webkit-transform: scale(1.1);
79 | -ms-transform: scale(1.1);
80 | transform: scale(1.1);
81 | }
82 | .sk-folding-cube .sk-cube:before {
83 | content: '';
84 | position: absolute;
85 | top: 0;
86 | left: 0;
87 | width: 100%;
88 | height: 100%;
89 | background-color: #333;
90 | -webkit-animation: sk-foldCubeAngle 2.4s infinite linear both;
91 | animation: sk-foldCubeAngle 2.4s infinite linear both;
92 | -webkit-transform-origin: 100% 100%;
93 | -ms-transform-origin: 100% 100%;
94 | transform-origin: 100% 100%;
95 | }
96 | .sk-folding-cube .sk-cube2 {
97 | -webkit-transform: scale(1.1) rotateZ(90deg);
98 | transform: scale(1.1) rotateZ(90deg);
99 | }
100 | .sk-folding-cube .sk-cube3 {
101 | -webkit-transform: scale(1.1) rotateZ(180deg);
102 | transform: scale(1.1) rotateZ(180deg);
103 | }
104 | .sk-folding-cube .sk-cube4 {
105 | -webkit-transform: scale(1.1) rotateZ(270deg);
106 | transform: scale(1.1) rotateZ(270deg);
107 | }
108 | .sk-folding-cube .sk-cube2:before {
109 | -webkit-animation-delay: 0.3s;
110 | animation-delay: 0.3s;
111 | }
112 | .sk-folding-cube .sk-cube3:before {
113 | -webkit-animation-delay: 0.6s;
114 | animation-delay: 0.6s;
115 | }
116 | .sk-folding-cube .sk-cube4:before {
117 | -webkit-animation-delay: 0.9s;
118 | animation-delay: 0.9s;
119 | }
120 | @-webkit-keyframes sk-foldCubeAngle {
121 | 0%,
122 | 10% {
123 | -webkit-transform: perspective(140px) rotateX(-180deg);
124 | transform: perspective(140px) rotateX(-180deg);
125 | opacity: 0;
126 | }
127 | 25%,
128 | 75% {
129 | -webkit-transform: perspective(140px) rotateX(0deg);
130 | transform: perspective(140px) rotateX(0deg);
131 | opacity: 1;
132 | }
133 | 90%,
134 | 100% {
135 | -webkit-transform: perspective(140px) rotateY(180deg);
136 | transform: perspective(140px) rotateY(180deg);
137 | opacity: 0;
138 | }
139 | }
140 |
141 | @keyframes sk-foldCubeAngle {
142 | 0%,
143 | 10% {
144 | -webkit-transform: perspective(140px) rotateX(-180deg);
145 | transform: perspective(140px) rotateX(-180deg);
146 | opacity: 0;
147 | }
148 | 25%,
149 | 75% {
150 | -webkit-transform: perspective(140px) rotateX(0deg);
151 | transform: perspective(140px) rotateX(0deg);
152 | opacity: 1;
153 | }
154 | 90%,
155 | 100% {
156 | -webkit-transform: perspective(140px) rotateY(180deg);
157 | transform: perspective(140px) rotateY(180deg);
158 | opacity: 0;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Link, navigate } from '@reach/router';
3 | import './App.css';
4 | import {
5 | useIdentityContext,
6 | IdentityContextProvider,
7 | // Settings,
8 | } from 'react-netlify-identity';
9 | import useLoading from './useLoading';
10 |
11 | type MaybePathProps = { path?: string };
12 | function PrivateRoute(
13 | props: React.PropsWithoutRef<
14 | MaybePathProps & { as: React.ComponentType }
15 | >
16 | ) {
17 | const identity = useIdentityContext();
18 | let { as: Comp } = props;
19 | return identity.user ? (
20 |
21 | ) : (
22 |
23 |
You are trying to view a protected page. Please log in
24 |
25 |
26 | );
27 | }
28 |
29 | // eslint-disable-next-line
30 | function Login({ }: MaybePathProps) {
31 | const {
32 | loginUser,
33 | signupUser,
34 | settings,
35 | loginProvider,
36 | } = useIdentityContext();
37 | const formRef = React.useRef(null!);
38 | const [msg, setMsg] = React.useState('');
39 | const [isLoading, load] = useLoading();
40 | const signup = () => {
41 | const email = formRef.current.email.value;
42 | const password = formRef.current.password.value;
43 | load(
44 | signupUser(email, password, {
45 | data: 'signed up thru react-netlify-identity',
46 | })
47 | )
48 | .then(user => {
49 | console.log('Success! Signed up', user);
50 | navigate('/dashboard');
51 | })
52 | .catch(err => void console.error(err) || setMsg('Error: ' + err.message));
53 | };
54 | return (
55 |
126 | );
127 | }
128 |
129 | // eslint-disable-next-line
130 | function Home({ }: MaybePathProps) {
131 | return (
132 |
133 |
Welcome to the Home page!
134 |
135 | this is a Public Page , not behind an authentication wall
136 |
137 |
153 |
154 | );
155 | }
156 | // eslint-disable-next-line
157 | function About({ }: MaybePathProps) {
158 | return About
;
159 | }
160 | // eslint-disable-next-line
161 | function Dashboard({ }: MaybePathProps) {
162 | const props = useIdentityContext();
163 | const { isConfirmedUser, authedFetch } = props;
164 | const [isLoading, load] = useLoading();
165 | const [msg, setMsg] = React.useState('Click to load something');
166 | const handler = () => {
167 | load(authedFetch.get('/.netlify/functions/authEndPoint')).then(setMsg);
168 | };
169 | return (
170 |
171 |
This is a Protected Dashboard!
172 | {!isConfirmedUser && (
173 |
174 | You have not confirmed your email. Please confirm it before you ping
175 | the API.
176 |
177 | )}
178 |
179 |
180 |
You can try pinging our authenticated API here.
181 |
182 | If you are logged in, you should be able to see a `user` info here.
183 |
184 |
Ping authenticated API
185 | {isLoading ?
:
{JSON.stringify(msg, null, 2)} }
186 |
187 |
188 | );
189 | }
190 |
191 | function Spinner() {
192 | return (
193 |
194 |
195 |
196 |
197 |
198 |
199 | );
200 | }
201 | function Nav() {
202 | const { isLoggedIn } = useIdentityContext();
203 | return (
204 |
205 | Home | Dashboard
206 | {' | '}
207 |
208 | {isLoggedIn ? : Log In/Sign Up}
209 |
210 |
211 | );
212 | }
213 | function Logout() {
214 | const { logoutUser } = useIdentityContext();
215 | return You are signed in. Log Out ;
216 | }
217 |
218 | function App() {
219 | // TODO: SUPPLY A URL EITHER FROM ENVIRONMENT VARIABLES OR SOME OTHER STRATEGY
220 | // e.g. 'https://unruffled-roentgen-04c3b8.netlify.com'
221 | const domainToUse =
222 | new URL(window.location.origin).hostname === 'localhost'
223 | ? 'http://react-netlify-identity.netlify.com'
224 | : window.location.origin;
225 | const [url, setUrl] = React.useState(domainToUse);
226 | return (
227 |
228 |
229 |
230 |
231 | Netlify Identity
232 | & Reach Router
233 |
234 |
235 |
236 | Netlify Identity
237 | {' '}
238 | Instance:{' '}
239 | setUrl(e.target.value)}
244 | size={50}
245 | />
246 |
247 |
248 | {window.location.hostname === 'localhost' ? (
249 |
WARNING: this demo doesn't work on localhost
250 | ) : (
251 |
252 | your instance here e.g.
253 | https://unruffled-roentgen-04c3b8.netlify.com
254 |
255 | )}
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 | last updated react-netlify-identity v0.1.7
269 |
270 | );
271 | }
272 |
273 | export default App;
274 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render( , document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/useLoading.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | export default function useLoading() {
4 | const [isLoading, setState] = React.useState(false)
5 | const mount = React.useRef(false)
6 | React.useEffect(() => {
7 | mount.current = true
8 | return () => void (mount.current = false)
9 | }, [])
10 | function load(aPromise: Promise ) {
11 | setState(true)
12 | return aPromise.finally(() => mount.current && setState(false))
13 | }
14 | return [isLoading, load] as [boolean, (aPromise: Promise ) => Promise ]
15 | }
16 |
--------------------------------------------------------------------------------
/examples/example-reach-router/src/useLocalState.tsx:
--------------------------------------------------------------------------------
1 | import React from "react"
2 |
3 | const noop: Function = () => {}
4 | export default function useLocalStorage(key: string, optionalCallback = noop) {
5 | const [state, setState] = React.useState(null)
6 | React.useEffect(() => {
7 | // chose to make this async
8 | const existingValue = localStorage.getItem(key)
9 | if (existingValue) {
10 | const parsedValue = JSON.parse(existingValue)
11 | setState(parsedValue)
12 | optionalCallback(parsedValue)
13 | }
14 | }, [])
15 | const removeItem = () => {
16 | setState(null)
17 | localStorage.removeItem(key)
18 | optionalCallback(null)
19 | }
20 | const setItem = (obj: any) => {
21 | setState(obj)
22 | localStorage.setItem(key, JSON.stringify(obj))
23 | optionalCallback(obj)
24 | }
25 | return [state, setItem, removeItem]
26 | }
27 |
--------------------------------------------------------------------------------
/examples/example-reach-router/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "jsx": "react",
7 | "moduleResolution": "node",
8 | "noImplicitAny": false,
9 | "noUnusedLocals": false,
10 | "noUnusedParameters": false,
11 | "removeComments": true,
12 | "strictNullChecks": true,
13 | "preserveConstEnums": true,
14 | "sourceMap": true,
15 | "lib": ["es2015", "es2016", "dom"],
16 | "baseUrl": ".",
17 | "types": ["node"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-react-router/README.md:
--------------------------------------------------------------------------------
1 | # React Netlify Identity Example
2 |
3 | This is an example of using `react-netlify-identity` with:
4 |
5 | - TypeScript
6 | - React
7 | - React Router
8 | - Netlify Identity
9 |
10 | Additionally, styling with:
11 |
12 | - styled-components
13 |
14 | Deployed demo site [here](https://react-netlify-identity-example.netlify.com/).
15 |
--------------------------------------------------------------------------------
/examples/example-react-router/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-netlify-identity-example",
3 | "version": "1.0.0",
4 | "author": "Lewis Llobera ",
5 | "license": "MIT",
6 | "dependencies": {
7 | "react": "^16.10.1",
8 | "react-dom": "^16.10.1",
9 | "react-netlify-identity": "^0.1.9",
10 | "react-router-dom": "^5.1.2"
11 | },
12 | "devDependencies": {
13 | "@types/react": "^16.9.4",
14 | "@types/react-dom": "^16.9.1",
15 | "@types/react-router-dom": "^5.1.0",
16 | "styled-components": "^4.4.0",
17 | "typescript": "^3.6.2"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
3 | import {
4 | IdentityContextProvider,
5 | useIdentityContext,
6 | } from 'react-netlify-identity';
7 |
8 | import { GlobalStyles } from './components';
9 | import { CreateAccount, Home, LogIn, Welcome } from './views';
10 |
11 | interface Props {
12 | component: React.FunctionComponent;
13 | exact?: boolean;
14 | path: string;
15 | }
16 |
17 | const PublicRoute: React.FunctionComponent = (props: Props) => {
18 | const { isLoggedIn } = useIdentityContext();
19 | return isLoggedIn ? : ;
20 | };
21 |
22 | const PrivateRoute: React.FunctionComponent = (props: Props) => {
23 | const { isLoggedIn } = useIdentityContext();
24 | return isLoggedIn ? : ;
25 | };
26 |
27 | export const App: React.FunctionComponent = () => {
28 | const url = 'https://react-netlify-identity-example.netlify.com';
29 |
30 | return (
31 | <>
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | >
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/assets/googleLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | btn_google_dark_normal_ios
5 | Created with Sketch.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/assets/index.ts:
--------------------------------------------------------------------------------
1 | import googleLogo from './googleLogo.svg';
2 |
3 | export { googleLogo };
4 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/AuthOption/AuthOption.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AuthOption = styled.div`
4 | align-items: center;
5 | display: flex;
6 | flex-direction: column;
7 | margin: 0 auto 10vh auto;
8 | width: 100%;
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/AuthOption/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthOption } from './AuthOption.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/AuthText/AuthText.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const AuthText = styled.p`
4 | border-bottom: 1px solid lightgray;
5 | display: block;
6 | font-weight: 700;
7 | line-height: 1.4;
8 | margin-bottom: 3vh;
9 | text-align: center;
10 | width: 100%;
11 | `;
12 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/AuthText/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthText } from './AuthText.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Button/Button.styles.ts:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | const animation = keyframes`
6 | from {
7 | transform: scale(1);
8 | }
9 | to {
10 | transform: scale(0.9);
11 | }
12 | `;
13 |
14 | interface Props {
15 | secondary?: boolean;
16 | }
17 |
18 | export const Button = styled.button`
19 | align-items: center;
20 | background-color: ${(props: Props): string =>
21 | props.secondary ? 'transparent' : 'var(--color-accent)'};
22 | border-radius: var(--radius-l);
23 | color: var(--color-dark);
24 | display: flex;
25 | font-weight: 700;
26 | height: 52px;
27 | justify-content: center;
28 | margin: 0 auto;
29 | transition: 0.5s;
30 | width: 300px;
31 |
32 | &:disabled {
33 | cursor: not-allowed;
34 | opacity: 0.5;
35 | }
36 |
37 | :active {
38 | animation: ${animation} 0.3s cubic-bezier(0.19, 1, 0.22, 1);
39 | }
40 |
41 | @media (max-width: ${BREAKPOINT}px) {
42 | width: 280px;
43 | }
44 | `;
45 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Button/index.ts:
--------------------------------------------------------------------------------
1 | export { Button } from './Button.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/ButtonGoogle/ButtonGoogle.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { Button } from '../../components';
4 |
5 | export const Logo = styled.img`
6 | margin-right: 24px;
7 | `;
8 |
9 | export const StyledButton = styled(Button)`
10 | background-color: #4285f4;
11 | background-image: none;
12 | color: #ffffff;
13 | `;
14 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/ButtonGoogle/ButtonGoogle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useIdentityContext } from 'react-netlify-identity';
3 |
4 | import { Logo, StyledButton } from './ButtonGoogle.styles';
5 | import { googleLogo } from '../../assets';
6 |
7 | interface Props {
8 | children: string;
9 | }
10 |
11 | export const ButtonGoogle: React.FunctionComponent = (props: Props) => {
12 | const { loginProvider } = useIdentityContext();
13 |
14 | const logInWithGoogle = (): void => {
15 | loginProvider('google');
16 | };
17 |
18 | return (
19 |
20 |
21 | {props.children}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/ButtonGoogle/index.ts:
--------------------------------------------------------------------------------
1 | export { ButtonGoogle } from './ButtonGoogle';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Container/Container.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { PAGE_WIDTH } from '../../constants/constants';
4 |
5 | interface Props {
6 | between?: boolean;
7 | paddingBottom?: string;
8 | partial?: boolean;
9 | row?: boolean;
10 | }
11 |
12 | export const Container = styled.div`
13 | align-items: center;
14 | display: flex;
15 | flex-direction: ${(props: Props): string => (props.row ? 'row' : 'column')};
16 | justify-content: ${(props: Props): string =>
17 | props.between ? 'space-between' : 'center'};
18 | height: ${(props: Props): string => (props.partial ? '80vh' : '100%')};
19 | margin-left: auto;
20 | margin-right: auto;
21 | max-width: ${PAGE_WIDTH}px;
22 | padding-bottom: ${(props: Props): string => {
23 | switch (props.paddingBottom) {
24 | case 'large':
25 | return '200px';
26 | case 'medium':
27 | return '120px';
28 | default:
29 | return 'auto';
30 | }
31 | }};
32 | padding-left: var(--padding);
33 | padding-right: var(--padding);
34 | width: 100%;
35 | `;
36 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Container/index.ts:
--------------------------------------------------------------------------------
1 | export { Container } from './Container.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Form/Form.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | interface Props {
6 | narrow?: boolean;
7 | }
8 |
9 | export const Form = styled.form`
10 | backdrop-filter: blur(2px);
11 | background-color: var(--color-background-translucent);
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: center;
15 | width: ${(props: Props): string => (props.narrow ? '60%' : '100%')};
16 |
17 | @media (max-width: ${BREAKPOINT}px) {
18 | width: 100%;
19 | }
20 | `;
21 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Form/index.ts:
--------------------------------------------------------------------------------
1 | export { Form } from './Form.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/GlobalStyles/GlobalStyles.styles.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | export const GlobalStyles = createGlobalStyle`
6 | :root {
7 | --color-accent: hsl(165, 100%, 50%);
8 | --color-dark: hsl(0, 0%, 10%);
9 | --color-error: hsl(343, 100%, 45%);
10 | --color-light: hsl(70, 0%, 95%);
11 | --color-light-translucent: hsla(70, 0%, 95%, 0.85);
12 | --font-size-xxs: 15px;
13 | --font-size-xs: 17px;
14 | --font-size-s: 18px;
15 | --font-size-m: 22px;
16 | --font-size-l: 30px;
17 | --font-size-xl: 46px;
18 | --font-size-xxl: 60px;
19 | --padding: 4%;
20 | --radius-l: 10px;
21 | --radius-m: 6px;
22 | }
23 |
24 | html
25 | body {
26 | background-color: var(--color-light);
27 | color: var(--color-dark);
28 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
29 | font-size: var(--font-size-s);
30 | padding-top: 80px;
31 | -webkit-touch-callout: none;
32 | -webkit-user-select: none;
33 |
34 | @media (max-width: ${BREAKPOINT}px) {
35 | font-size: var(--font-size-xs);
36 | }
37 | }
38 |
39 | a {
40 | color: inherit;
41 | text-decoration: none;
42 | }
43 |
44 | [contenteditable] {
45 | user-select: text;
46 | }
47 |
48 | /* CSS Reset */
49 |
50 | * {
51 | box-sizing: border-box;
52 | -webkit-box-sizing: border-box;
53 | -webkit-tap-highlight-color: hsla(0,0%,0%,0);
54 | -webkit-tap-highlight-color: transparent;
55 | }
56 |
57 | html,
58 | body {
59 | margin: 0;
60 | height: 100%;
61 | width: 100%;
62 | -webkit-font-smoothing: antialiased;
63 | -moz-osx-font-smoothing: grayscale;
64 | }
65 |
66 | h1,
67 | h2,
68 | h3,
69 | h4,
70 | h5,
71 | h6,
72 | p {
73 | font-size: inherit;
74 | font-weight: inherit;
75 | margin: 0;
76 | margin-block-end: 0;
77 | margin-block-start: 0;
78 | margin-inline-end: 0;
79 | margin-inline-start: 0;
80 | -webkit-margin-after: 0;
81 | -webkit-margin-before: 0;
82 | -webkit-margin-end: 0;
83 | -webkit-margin-start: 0;
84 | }
85 |
86 | button {
87 | background-color: inherit;
88 | border: none;
89 | color: inherit;
90 | cursor: pointer;
91 | display: inline-block;
92 | font-family: inherit;
93 | font-size: inherit;
94 | margin: 0;
95 | padding: 0;
96 | text-align: center;
97 | text-decoration: none;
98 | -webkit-appearance: none;
99 | -moz-appearance: none;
100 | }
101 |
102 | button:focus {
103 | outline: 0;
104 | }
105 |
106 | input {
107 | border-color: initial;
108 | border-image: initial;
109 | border-style: none;
110 | color: inherit;
111 | display: block;
112 | font: inherit;
113 | padding: 10px;
114 | margin: 0;
115 | width: 100%;
116 | }
117 |
118 | input:focus {
119 | outline: 0;
120 | }
121 |
122 | input:not([type=checkbox]):not([type=radio]) {
123 | appearance: none;
124 | }
125 |
126 | ::-webkit-scrollbar {
127 | display: none;
128 | }
129 | `;
130 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/GlobalStyles/index.ts:
--------------------------------------------------------------------------------
1 | export { GlobalStyles } from './GlobalStyles.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Header/Header.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | export const FixedBar = styled.div`
6 | background-color: var(--color-light-translucent);
7 | backdrop-filter: blur(2px);
8 | height: 60px;
9 | left: 0;
10 | padding: 10px 0;
11 | position: fixed;
12 | right: 0;
13 | top: 0;
14 | z-index: 2;
15 | `;
16 |
17 | export const Title = styled.h1`
18 | font-size: var(--font-size-xl);
19 | font-weight: 700;
20 | margin-top: -5px;
21 |
22 | @media (max-width: ${BREAKPOINT}px) {
23 | font-size: var(--font-size-l);
24 | }
25 | `;
26 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Header/Header.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { FixedBar, Title } from './Header.styles';
4 | import { Container } from '../../components';
5 |
6 | interface Props {
7 | name?: string;
8 | }
9 |
10 | export const Header: React.FunctionComponent = (props: Props) => {
11 | if (props.name) {
12 | return (
13 |
14 |
15 | {props.name}
16 |
17 |
18 | );
19 | } else {
20 | return (
21 |
22 |
23 | Home
24 |
25 |
26 | );
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Header/index.ts:
--------------------------------------------------------------------------------
1 | export { Header } from './Header';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Input/Input.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Input = styled.input`
4 | background-color: white;
5 | border: 3px solid transparent;
6 | border-radius: var(--radius-m);
7 | height: 50px;
8 | margin: 0 auto 15px auto;
9 | padding: 0px 20px;
10 |
11 | :focus {
12 | border: 3px solid var(--color-accent-light);
13 | transition: border 0.2s ease-in;
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Input/index.ts:
--------------------------------------------------------------------------------
1 | export { Input } from './Input.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Label/Label.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | export const Label = styled.label`
6 | color: var(--color-text-lighter);
7 | font-size: var(--font-size-s);
8 | margin-bottom: 3px;
9 |
10 | @media (max-width: ${BREAKPOINT}px) {
11 | font-size: var(--font-size-xs);
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/Label/index.ts:
--------------------------------------------------------------------------------
1 | export { Label } from './Label.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/TextError/TextError.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const TextError = styled.p`
4 | color: var(--color-error);
5 | font-weight: 700;
6 | line-height: 1.4;
7 | margin-top: 3vh;
8 | text-align: center;
9 | `;
10 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/TextError/index.ts:
--------------------------------------------------------------------------------
1 | export { TextError } from './TextError.styles';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export { AuthOption } from './AuthOption';
2 | export { AuthText } from './AuthText';
3 | export { Button } from './Button';
4 | export { ButtonGoogle } from './ButtonGoogle';
5 | export { Container } from './Container';
6 | export { Form } from './Form';
7 | export { GlobalStyles } from './GlobalStyles';
8 | export { Header } from './Header';
9 | export { Input } from './Input';
10 | export { Label } from './Label';
11 | export { TextError } from './TextError';
12 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/constants/constants.ts:
--------------------------------------------------------------------------------
1 | export const BREAKPOINT = 600;
2 |
3 | export const PAGE_WIDTH = 800;
4 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { App } from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Netlify Identity Example
7 |
11 |
12 |
13 | This app needs JavaScript to run.
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow: /
4 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/CreateAccount/CreateAccount.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | export const PasswordTip = styled.span`
6 | color: var(--color-dark-lighter);
7 | font-size: var(--font-size-xs);
8 |
9 | @media (max-width: ${BREAKPOINT}px) {
10 | font-size: var(--font-size-xxs);
11 | }
12 | `;
13 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/CreateAccount/CreateAccount.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import { useIdentityContext } from 'react-netlify-identity';
4 |
5 | import { PasswordTip } from './CreateAccount.styles';
6 | import {
7 | AuthOption,
8 | AuthText,
9 | Button,
10 | ButtonGoogle,
11 | Container,
12 | Form,
13 | Input,
14 | Label,
15 | Header,
16 | TextError,
17 | } from '../../components';
18 |
19 | export const CreateAccount: React.FunctionComponent = () => {
20 | const { loginUser, signupUser } = useIdentityContext();
21 | const [error, setError] = useState(false);
22 | const emailInput = useRef(null!);
23 | const passwordInput = useRef(null!);
24 | const signUpButton = useRef(null!);
25 |
26 | useEffect(() => {
27 | signUpButton.current.disabled = true;
28 | }, [emailInput, passwordInput]);
29 |
30 | const passwordPattern = /^.{6,}$/;
31 |
32 | const handleChange = (): void => {
33 | const email = emailInput.current.value;
34 | const password = passwordInput.current.value;
35 | if (email && passwordPattern.test(password)) {
36 | signUpButton.current.disabled = false;
37 | } else {
38 | signUpButton.current.disabled = true;
39 | }
40 | };
41 |
42 | const signUp = (event: React.FormEvent): void => {
43 | event.preventDefault();
44 | const email = emailInput.current.value;
45 | const password = passwordInput.current.value;
46 | signupUser(email, password, {})
47 | .then(() => {
48 | loginUser(email, password, true);
49 | ;
50 | })
51 | .catch((error) => {
52 | setError(true);
53 | console.log(error);
54 | });
55 | };
56 |
57 | return (
58 | <>
59 |
60 |
61 |
62 | Sign up with email:
63 |
90 |
91 |
92 | Or sign up with Google:
93 | Sign up with Google
94 |
95 |
96 | >
97 | );
98 | };
99 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/CreateAccount/index.ts:
--------------------------------------------------------------------------------
1 | export { CreateAccount } from './CreateAccount';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Home/Home.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { Button } from '../../components';
4 |
5 | export const LogOutButton = styled(Button)`
6 | color: var(--color-error);
7 | margin-top: 10vh;
8 | `;
9 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useIdentityContext } from 'react-netlify-identity';
3 | import { Redirect } from 'react-router-dom';
4 |
5 | import { Container, Header } from '../../components';
6 | import { LogOutButton } from './Home.styles';
7 |
8 | export const Home: React.FunctionComponent = () => {
9 | const { user, logoutUser } = useIdentityContext();
10 |
11 | const logOut = (): void => {
12 | logoutUser();
13 | ;
14 | };
15 |
16 | return (
17 | <>
18 |
19 |
20 |
21 | Hello {JSON.stringify(user!.email)}
22 |
23 |
24 | Log out
25 |
26 |
27 | >
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Home/index.ts:
--------------------------------------------------------------------------------
1 | export { Home } from './Home';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/LogIn/LogIn.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import { Redirect } from 'react-router-dom';
3 | import { useIdentityContext } from 'react-netlify-identity';
4 |
5 | import {
6 | AuthOption,
7 | AuthText,
8 | Button,
9 | ButtonGoogle,
10 | Container,
11 | Form,
12 | Input,
13 | Label,
14 | Header,
15 | TextError,
16 | } from '../../components';
17 |
18 | export const LogIn: React.FunctionComponent = () => {
19 | const { loginUser } = useIdentityContext();
20 | const [error, setError] = useState(false);
21 | const emailInput = useRef(null!);
22 | const passwordInput = useRef(null!);
23 | const logInButton = useRef(null!);
24 |
25 | useEffect(() => {
26 | logInButton.current.disabled = true;
27 | }, [emailInput, passwordInput]);
28 |
29 | const handleChange = (): void => {
30 | const email = emailInput.current.value;
31 | const password = passwordInput.current.value;
32 | if (email && password) {
33 | logInButton.current.disabled = false;
34 | } else {
35 | logInButton.current.disabled = true;
36 | }
37 | };
38 |
39 | const logIn = (event: React.FormEvent): void => {
40 | event.preventDefault();
41 | const email = emailInput.current.value;
42 | const password = passwordInput.current.value;
43 | loginUser(email, password, true)
44 | .then(() => {
45 | ;
46 | })
47 | .catch((error) => {
48 | setError(true);
49 | console.log(error);
50 | });
51 | };
52 |
53 | return (
54 | <>
55 |
56 |
57 |
58 | Log in with email:
59 |
84 |
85 |
86 | Or log in with Google:
87 | Log in with Google
88 |
89 |
90 | >
91 | );
92 | };
93 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/LogIn/index.ts:
--------------------------------------------------------------------------------
1 | export { LogIn } from './LogIn';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Welcome/Welcome.styles.ts:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | import { BREAKPOINT } from '../../constants/constants';
4 |
5 | export const Intro = styled.h1`
6 | font-weight: 700;
7 | font-size: var(--font-size-xxl);
8 | margin-bottom: 2vh;
9 | text-align: center;
10 |
11 | @media (max-width: ${BREAKPOINT}px) {
12 | font-size: var(--font-size-xl);
13 | }
14 | `;
15 |
16 | export const Layout = styled.div`
17 | display: flex;
18 | justify-content: center;
19 | width: 100%;
20 | `;
21 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Welcome/Welcome.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import { Intro, Layout } from './Welcome.styles';
5 | import { Button, Container } from '../../components';
6 |
7 | export const Welcome: React.FunctionComponent = () => {
8 | return (
9 | <>
10 |
11 | React Netlify Identity Example
12 |
13 | This is made with: TypeScript, React, React Router, Netlify Identity,
14 | and styled-components.
15 |
16 |
17 |
18 | Log in
19 |
20 |
21 |
22 |
23 | Create account
24 |
25 |
26 |
27 | >
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/Welcome/index.ts:
--------------------------------------------------------------------------------
1 | export { Welcome } from './Welcome';
2 |
--------------------------------------------------------------------------------
/examples/example-react-router/src/views/index.tsx:
--------------------------------------------------------------------------------
1 | export { CreateAccount } from './CreateAccount';
2 | export { Home } from './Home';
3 | export { LogIn } from './LogIn';
4 | export { Welcome } from './Welcome';
5 |
--------------------------------------------------------------------------------
/examples/example-react-router/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "isolatedModules": true,
6 | "jsx": "react",
7 | "module": "esnext",
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "target": "es5"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | # example netlify.toml
2 | [build]
3 | command = "cd example && mkdir dist && cp public/_redirects dist/_redirects && yarn && SKIP_PREFLIGHT_CHECK=true yarn build"
4 | functions = "example/functions"
5 | publish = "example/dist"
6 |
7 | ## Uncomment to use this redirect for Single Page Applications like create-react-app.
8 | ## Not needed for static site generators.
9 | #[[redirects]]
10 | # from = "/*"
11 | # to = "/index.html"
12 | # status = 200
13 |
14 | ## (optional) Settings for Netlify Dev
15 | ## https://github.com/netlify/netlify-dev-plugin#project-detection
16 | #[dev]
17 | # command = "yarn start" # Command to start your dev server
18 | # port = 3000 # Port that the dev server will be listening on
19 | # publish = "dist" # Folder with the static content for _redirect file
20 |
21 | ## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-netlify-identity",
3 | "version": "0.2.7",
4 | "description": "use netlify identity with react",
5 | "author": "sw-yx",
6 | "license": "MIT",
7 | "repository": "https://www.github.com/sw-yx/react-netlify-identity",
8 | "main": "dist/index.js",
9 | "umd:main": "dist/react-netlify-identity.umd.production.js",
10 | "module": "dist/react-netlify-identity.esm.js",
11 | "typings": "dist/index.d.ts",
12 | "jsnext:main": "dist/index.es.js",
13 | "engines": {
14 | "node": ">=8",
15 | "npm": ">=5"
16 | },
17 | "scripts": {
18 | "start": "tsdx watch",
19 | "build": "tsdx build",
20 | "unused_prepare": "yarn run build",
21 | "version": "yarn run build && auto-changelog -p --template keepachangelog && git add .",
22 | "prepublishOnly": "git push && git push --tags && gh-release",
23 | "predeploy": "cd example && yarn install && yarn run build",
24 | "deploy": "gh-pages -d example/build"
25 | },
26 | "dependencies": {
27 | "gotrue-js": "^0.9.25"
28 | },
29 | "peerDependencies": {
30 | "react": "^15.0.0 || ^16.0.0",
31 | "react-dom": "^15.0.0 || ^16.0.0"
32 | },
33 | "husky": {
34 | "hooks": {
35 | "pre-commit": "pretty-quick --staged"
36 | }
37 | },
38 | "prettier": {
39 | "printWidth": 80,
40 | "semi": true,
41 | "singleQuote": true,
42 | "trailingComma": "es5"
43 | },
44 | "devDependencies": {
45 | "@svgr/rollup": "^5.0.1",
46 | "@types/jest": "^24.0.25",
47 | "@types/react": "^16.9.17",
48 | "@types/react-dom": "^16.9.4",
49 | "auto-changelog": "^1.16.2",
50 | "gh-release": "^3.5.0",
51 | "husky": "^4.0.9",
52 | "prettier": "^1.19.1",
53 | "pretty-quick": "^2.0.1",
54 | "react": "^16.12.0",
55 | "react-dom": "^16.12.0",
56 | "tsdx": "^0.12.3",
57 | "typescript": "^3.7.4"
58 | },
59 | "files": [
60 | "dist"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | useMemo,
4 | useEffect,
5 | createContext,
6 | useContext,
7 | useCallback,
8 | // types
9 | ReactNode,
10 | } from 'react';
11 |
12 | import GoTrue, {
13 | User as GoTrueUser,
14 | Settings as GoTrueSettings,
15 | } from 'gotrue-js';
16 | import { runRoutes } from './runRoutes';
17 | import { TokenParam, defaultParam } from './token';
18 |
19 | type authChangeParam = (user?: User) => string | void;
20 |
21 | export type Settings = GoTrueSettings;
22 | export type User = GoTrueUser;
23 | type Provider = 'bitbucket' | 'github' | 'gitlab' | 'google';
24 |
25 | const defaultSettings = {
26 | autoconfirm: false,
27 | disable_signup: false,
28 | external: {
29 | bitbucket: false,
30 | email: true,
31 | facebook: false,
32 | github: false,
33 | gitlab: false,
34 | google: false,
35 | },
36 | };
37 |
38 | const errors = {
39 | noUserFound: 'No current user found - are you logged in?',
40 | noUserTokenFound: 'no user token found',
41 | tokenMissingOrInvalid: 'either no token found or invalid for this purpose',
42 | };
43 |
44 | export type ReactNetlifyIdentityAPI = {
45 | user: User | undefined;
46 | /** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */
47 | setUser: (_user: GoTrueUser | undefined) => GoTrueUser | undefined;
48 | isConfirmedUser: boolean;
49 | isLoggedIn: boolean;
50 | signupUser: (
51 | email: string,
52 | password: string,
53 | data: Object,
54 | directLogin?: boolean
55 | ) => Promise;
56 | loginUser: (
57 | email: string,
58 | password: string,
59 | remember?: boolean
60 | ) => Promise;
61 | logoutUser: () => Promise;
62 | requestPasswordRecovery: (email: string) => Promise;
63 | recoverAccount: (remember?: boolean) => Promise;
64 | updateUser: (fields: object) => Promise;
65 | getFreshJWT: () => Promise | undefined;
66 | authedFetch: {
67 | get: (endpoint: string, obj?: RequestInit) => Promise;
68 | post: (endpoint: string, obj?: RequestInit) => Promise;
69 | put: (endpoint: string, obj?: RequestInit) => Promise;
70 | delete: (endpoint: string, obj?: RequestInit) => Promise;
71 | };
72 | _goTrueInstance: GoTrue;
73 | _url: string;
74 | loginProvider: (provider: Provider) => void;
75 | acceptInviteExternalUrl: (
76 | provider: Provider,
77 | autoRedirect: boolean
78 | ) => string | undefined;
79 | settings: Settings;
80 | param: TokenParam;
81 | verifyToken: () => Promise;
82 | };
83 |
84 | const [_useIdentityContext, _IdentityCtxProvider] = createCtx<
85 | ReactNetlifyIdentityAPI
86 | >();
87 | export const useIdentityContext = _useIdentityContext; // we dont want to expose _IdentityCtxProvider
88 |
89 | /** most people should use this provider directly */
90 | export function IdentityContextProvider({
91 | url,
92 | children,
93 | onAuthChange = () => {},
94 | }: {
95 | url: string;
96 | children: ReactNode;
97 | onAuthChange?: authChangeParam;
98 | }) {
99 | /******** SETUP */
100 | if (!url || !validateUrl(url)) {
101 | // just a safety check in case a JS user tries to skip this
102 | throw new Error(
103 | 'invalid netlify instance URL: ' +
104 | url +
105 | '. Please check the docs for proper usage or file an issue.'
106 | );
107 | }
108 | const identity = useNetlifyIdentity(url, onAuthChange);
109 | return (
110 | <_IdentityCtxProvider value={identity}>{children}
111 | );
112 | }
113 |
114 | /** some people may want to use this as a hook and bring their own contexts */
115 | export function useNetlifyIdentity(
116 | url: string,
117 | onAuthChange: authChangeParam = () => {},
118 | enableRunRoutes: boolean = true
119 | ): ReactNetlifyIdentityAPI {
120 | const goTrueInstance = useMemo(
121 | () =>
122 | new GoTrue({
123 | APIUrl: `${url}/.netlify/identity`,
124 | setCookie: true,
125 | }),
126 | [url]
127 | );
128 |
129 | /******* STATE and EFFECTS */
130 |
131 | const [user, setUser] = useState(
132 | goTrueInstance.currentUser() || undefined
133 | );
134 |
135 | const _setUser = useCallback(
136 | (_user: User | undefined) => {
137 | setUser(_user);
138 | onAuthChange(_user); // if someone's subscribed to auth changes, let 'em know
139 | return _user; // so that we can continue chaining
140 | },
141 | [onAuthChange]
142 | );
143 |
144 | const [param, setParam] = useState(defaultParam);
145 |
146 | useEffect(() => {
147 | if (enableRunRoutes) {
148 | const param = runRoutes(goTrueInstance, _setUser);
149 |
150 | if (param.token || param.error) {
151 | setParam(param);
152 | }
153 | }
154 | }, []);
155 |
156 | const [settings, setSettings] = useState(defaultSettings);
157 |
158 | useEffect(() => {
159 | goTrueInstance.settings
160 | .bind(goTrueInstance)()
161 | .then(x => setSettings(x));
162 | }, []);
163 |
164 | /******* OPERATIONS */
165 | // make sure the Registration preferences under Identity settings in your Netlify dashboard are set to Open.
166 | // https://react-netlify-identity.netlify.com/login#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NTY0ODY3MjEsInN1YiI6ImNiZjY5MTZlLTNlZGYtNGFkNS1iOTYzLTQ4ZTY2NDcyMDkxNyIsImVtYWlsIjoic2hhd250aGUxQGdtYWlsLmNvbSIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImdpdGh1YiJ9LCJ1c2VyX21ldGFkYXRhIjp7ImF2YXRhcl91cmwiOiJodHRwczovL2F2YXRhcnMxLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzY3NjQ5NTc_dj00IiwiZnVsbF9uYW1lIjoic3d5eCJ9fQ.E8RrnuCcqq-mLi1_Q5WHJ-9THIdQ3ha1mePBKGhudM0&expires_in=3600&refresh_token=OyA_EdRc7WOIVhY7RiRw5w&token_type=bearer
167 | /******* external oauth */
168 |
169 | /**
170 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L71
171 | */
172 | const loginProvider = useCallback(
173 | (provider: Provider) => {
174 | const url = goTrueInstance.loginExternalUrl(provider);
175 | window.location.href = url;
176 | },
177 | [goTrueInstance]
178 | );
179 |
180 | /**
181 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L92
182 | */
183 | const acceptInviteExternalUrl = useCallback(
184 | (provider: Provider, autoRedirect: boolean = true) => {
185 | if (!param.token || param.type !== 'invite') {
186 | console.error(errors.tokenMissingOrInvalid);
187 | return;
188 | }
189 |
190 | const url = goTrueInstance.acceptInviteExternalUrl(provider, param.token);
191 | // clean up consumed token
192 | setParam(defaultParam);
193 |
194 | if (autoRedirect) {
195 | window.location.href = url;
196 | return;
197 | }
198 |
199 | return url;
200 | },
201 | [goTrueInstance, param]
202 | );
203 |
204 | /**
205 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L123
206 | */
207 | const verifyToken = useCallback(() => {
208 | if (!param.type || !param.token) {
209 | return Promise.reject(errors.tokenMissingOrInvalid);
210 | }
211 |
212 | return goTrueInstance.verify(param.type, param.token).then(user => {
213 | // cleanup consumed token
214 | setParam(defaultParam);
215 |
216 | return user;
217 | });
218 | }, [goTrueInstance, param]);
219 |
220 | /******* email auth */
221 | /**
222 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L50
223 | */
224 | const signupUser = useCallback(
225 | (
226 | email: string,
227 | password: string,
228 | data: Object,
229 | directLogin: boolean = true
230 | ) =>
231 | goTrueInstance.signup(email, password, data).then(user => {
232 | if (directLogin) {
233 | return _setUser(user);
234 | }
235 |
236 | return user;
237 | }),
238 | [goTrueInstance, _setUser]
239 | );
240 |
241 | /**
242 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L57
243 | */
244 | const loginUser = useCallback(
245 | (email: string, password: string, remember: boolean = true) =>
246 | goTrueInstance.login(email, password, remember).then(_setUser),
247 | [goTrueInstance, _setUser]
248 | );
249 |
250 | /**
251 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L80
252 | */
253 | const requestPasswordRecovery = useCallback(
254 | (email: string) => goTrueInstance.requestPasswordRecovery(email),
255 | [goTrueInstance]
256 | );
257 |
258 | /**
259 | * @see https://github.com/netlify/gotrue-js/blob/master/src/index.js#L87
260 | */
261 | const recoverAccount = useCallback(
262 | (remember?: boolean) => {
263 | if (!param.token || param.type !== 'recovery') {
264 | return Promise.reject(errors.tokenMissingOrInvalid);
265 | }
266 |
267 | return goTrueInstance
268 | .recover(param.token, remember)
269 | .then(user => {
270 | return _setUser(user);
271 | })
272 | .finally(() => {
273 | // clean up consumed token
274 | setParam(defaultParam);
275 | });
276 | },
277 | [goTrueInstance, _setUser, param]
278 | );
279 |
280 | /**
281 | * @see https://github.com/netlify/gotrue-js/blob/master/src/user.js#L54
282 | */
283 | const updateUser = useCallback(
284 | (fields: object) => {
285 | if (!user) {
286 | return Promise.reject(errors.noUserFound);
287 | }
288 |
289 | return user!
290 | .update(fields) // e.g. { email: "example@example.com", password: "password" }
291 | .then(_setUser);
292 | },
293 | [user]
294 | );
295 |
296 | /**
297 | * @see https://github.com/netlify/gotrue-js/blob/master/src/user.js#L63
298 | */
299 | const getFreshJWT = useCallback(() => {
300 | if (!user) {
301 | return Promise.reject(errors.noUserFound);
302 | }
303 |
304 | return user.jwt();
305 | }, [user]);
306 |
307 | /**
308 | * @see https://github.com/netlify/gotrue-js/blob/master/src/user.js#L71
309 | */
310 | const logoutUser = useCallback(() => {
311 | if (!user) {
312 | return Promise.reject(errors.noUserFound);
313 | }
314 |
315 | return user.logout().then(() => _setUser(undefined));
316 | }, [user]);
317 |
318 | const genericAuthedFetch = (method: RequestInit['method']) => (
319 | endpoint: string,
320 | options: RequestInit = {}
321 | ) => {
322 | if (!user?.token?.access_token) {
323 | return Promise.reject(errors.noUserTokenFound);
324 | }
325 |
326 | const defaultObj = {
327 | headers: {
328 | Accept: 'application/json',
329 | 'Content-Type': 'application/json',
330 | Authorization: 'Bearer ' + user.token.access_token,
331 | },
332 | };
333 | const finalObj = Object.assign(defaultObj, { method }, options);
334 |
335 | return fetch(endpoint, finalObj).then(res =>
336 | finalObj.headers['Content-Type'] === 'application/json' ? res.json() : res
337 | );
338 | };
339 |
340 | const authedFetch = {
341 | get: genericAuthedFetch('GET'),
342 | post: genericAuthedFetch('POST'),
343 | put: genericAuthedFetch('PUT'),
344 | delete: genericAuthedFetch('DELETE'),
345 | };
346 |
347 | /******* hook API */
348 | return {
349 | user,
350 | /** not meant for normal use! you should mostly use one of the other exported methods to update the user instance */
351 | setUser: _setUser,
352 | isConfirmedUser: !!(user && user.confirmed_at),
353 | isLoggedIn: !!user,
354 | signupUser,
355 | loginUser,
356 | logoutUser,
357 | requestPasswordRecovery,
358 | recoverAccount,
359 | updateUser,
360 | getFreshJWT,
361 | authedFetch,
362 | _goTrueInstance: goTrueInstance,
363 | _url: url,
364 | loginProvider,
365 | acceptInviteExternalUrl,
366 | settings,
367 | param,
368 | verifyToken,
369 | };
370 | }
371 |
372 | /**
373 | *
374 | *
375 | * Utils
376 | *
377 | */
378 |
379 | function validateUrl(value: string) {
380 | return /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
381 | value
382 | );
383 | }
384 |
385 | // lazy initialize contexts without providing a Nullable type upfront
386 | function createCtx() {
387 | const ctx = createContext (undefined);
388 | function useCtx() {
389 | const c = useContext(ctx);
390 | if (!c) throw new Error('useCtx must be inside a Provider with a value');
391 | return c;
392 | }
393 | return [useCtx, ctx.Provider] as const;
394 | }
395 |
396 | // // Deprecated for now
397 | // interface NIProps {
398 | // children: any
399 | // url: string
400 | // onAuthChange?: authChangeParam
401 | // }
402 | // export default function NetlifyIdentity({ children, url, onAuthChange }: NIProps) {
403 | // return children(useNetlifyIdentity(url, onAuthChange))
404 | // }
405 |
--------------------------------------------------------------------------------
/src/runRoutes.tsx:
--------------------------------------------------------------------------------
1 | import GoTrue, { User } from 'gotrue-js';
2 | import { TokenParam, defaultParam } from './token';
3 |
4 | /**
5 | * This code runs on every rerender so keep it light
6 | * keep checking the current route and do logic based on the route
7 | * as dictated by netlify identity's communication with us via hashes
8 | */
9 |
10 | const routes = /(confirmation|invite|recovery|email_change|access)_token=([^&]+)/;
11 | const errorRoute = /error=access_denied&error_description=403/;
12 |
13 | const reduceHashToKeyValue = (hash: string): { [key: string]: string } =>
14 | hash.split('&').reduce((carry, pair) => {
15 | const [key, value] = pair.split('=');
16 |
17 | return { ...carry, [key]: value };
18 | }, {});
19 |
20 | const hashReplace = /^#\/?/;
21 |
22 | export function runRoutes(
23 | gotrue: GoTrue,
24 | setUser: (value: User) => User | undefined,
25 | remember = true
26 | ): TokenParam {
27 | // early terminate if no hash
28 | // also accounts for document.cookie further down
29 | if (!document?.location?.hash) {
30 | return defaultParam;
31 | }
32 |
33 | const hash = document.location.hash.replace(hashReplace, '');
34 |
35 | try {
36 | history.pushState(
37 | '',
38 | document.title,
39 | window.location.pathname + window.location.search
40 | );
41 | } catch (_) {
42 | window.location.href.substr(0, window.location.href.indexOf('#'));
43 | }
44 |
45 | // earliest possible bail on any match
46 | if (hash.match(errorRoute)) {
47 | return {
48 | ...defaultParam,
49 | error: 'access_denied',
50 | status: 403,
51 | };
52 | }
53 |
54 | const matchesActionHashes = hash.match(routes);
55 |
56 | if (matchesActionHashes) {
57 | const params = reduceHashToKeyValue(hash);
58 |
59 | if (params.confirmation_token) {
60 | gotrue
61 | .confirm(params.confirmation_token, remember)
62 | .then(setUser)
63 | .catch(console.error);
64 |
65 | // dont notify dev as this package does not export its own method for this
66 | return defaultParam;
67 | }
68 |
69 | if (params.access_token) {
70 | document.cookie = `nf_jwt=${params.access_token}`;
71 |
72 | gotrue
73 | .createUser(params, remember)
74 | .then(setUser)
75 | .catch(console.error);
76 |
77 | // also dont notify dev here for the same reasons as above
78 | return defaultParam;
79 | }
80 |
81 | // pass responsibility to dev in all other cases
82 | return {
83 | ...defaultParam,
84 | type: matchesActionHashes[1] as TokenParam['type'],
85 | token: matchesActionHashes[2],
86 | };
87 | }
88 |
89 | return defaultParam;
90 | }
91 |
--------------------------------------------------------------------------------
/src/token.ts:
--------------------------------------------------------------------------------
1 | export type TokenParam = {
2 | token: string | undefined;
3 | type: 'invite' | 'recovery' | 'email_change' | undefined;
4 | error: 'access_denied' | undefined;
5 | status: 403 | undefined;
6 | };
7 |
8 | export const defaultParam: TokenParam = {
9 | token: undefined,
10 | type: undefined,
11 | error: undefined,
12 | status: undefined,
13 | };
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types"],
3 | "compilerOptions": {
4 | "target": "es5",
5 | "module": "esnext",
6 | "lib": ["dom", "esnext"],
7 | "importHelpers": true,
8 | "declaration": true,
9 | "sourceMap": true,
10 | "rootDir": "./",
11 | "strict": true,
12 | "noImplicitAny": true,
13 | "strictNullChecks": true,
14 | "strictFunctionTypes": true,
15 | "strictPropertyInitialization": true,
16 | "noImplicitThis": true,
17 | "alwaysStrict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noImplicitReturns": true,
21 | "noFallthroughCasesInSwitch": true,
22 | "moduleResolution": "node",
23 | "baseUrl": "./",
24 | "paths": {
25 | "*": ["src/*", "node_modules/*"]
26 | },
27 | "jsx": "react",
28 | "esModuleInterop": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------