@rpcbase/client 0.11.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth/Footer.js +14 -0
- package/auth/ForgotPassword/forgot-password.scss +37 -0
- package/auth/ForgotPassword/index.js +117 -0
- package/auth/SetNewPassword/index.js +138 -0
- package/auth/SetNewPassword/set-new-password.scss +47 -0
- package/auth/SignIn/index.js +148 -0
- package/auth/SignIn/sign-in.scss +66 -0
- package/auth/SignOut/index.js +35 -0
- package/auth/SignUp/index.js +107 -0
- package/auth/SignUp/sign-up.scss +56 -0
- package/auth/index.js +72 -0
- package/helpers/post.js +20 -0
- package/package.json +2 -2
- package/rpc_post.js +0 -2
- package/auth/Comp.js +0 -13
package/auth/Footer.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
const year = (new Date).getFullYear()
|
|
4
|
+
|
|
5
|
+
const Footer = ({name}) => {
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<footer className="pt-5 my-5 text-muted border-top">
|
|
9
|
+
© {year} <span className="ms-1">{name}</span>
|
|
10
|
+
</footer>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default Footer
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#forgot-password-wrapper {
|
|
4
|
+
height: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-top: 40px;
|
|
9
|
+
padding-bottom: 40px;
|
|
10
|
+
// background-color: var(--bs-secondary);
|
|
11
|
+
background-color: $gray-500;
|
|
12
|
+
|
|
13
|
+
hr {
|
|
14
|
+
background-color: $gray-500;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
footer {
|
|
18
|
+
color: $light !important;
|
|
19
|
+
border-top: none !important;
|
|
20
|
+
padding-top: 0 !important;
|
|
21
|
+
margin-bottom: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
.form-forgot {
|
|
26
|
+
width: 100%;
|
|
27
|
+
max-width: 380px;
|
|
28
|
+
margin: auto;
|
|
29
|
+
background-color: $light;
|
|
30
|
+
border-radius: 22px;
|
|
31
|
+
border: 1px solid $gray-600;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.form-forgot .form-floating:focus-within {
|
|
35
|
+
z-index: 2;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useState, useEffect} from "react"
|
|
3
|
+
import {useHash} from "react-use"
|
|
4
|
+
import {ActivityIndicator} from "react-native"
|
|
5
|
+
import isEmail from "validator/lib/isEmail"
|
|
6
|
+
|
|
7
|
+
import post from "../../helpers/post"
|
|
8
|
+
|
|
9
|
+
import {set_is_signed_in} from "../index"
|
|
10
|
+
import Footer from "../Footer"
|
|
11
|
+
|
|
12
|
+
import "./forgot-password.scss"
|
|
13
|
+
|
|
14
|
+
const ForgotPassword = ({name, logo}) => {
|
|
15
|
+
const [hash, setHash] = useHash()
|
|
16
|
+
|
|
17
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
18
|
+
const [isSuccess, setIsSuccess] = useState(false)
|
|
19
|
+
const [errors, setErrors] = useState()
|
|
20
|
+
|
|
21
|
+
const [email, setEmail] = useState("")
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (hash?.length > 1) {
|
|
25
|
+
const val = hash.slice(1)
|
|
26
|
+
|
|
27
|
+
const decoded = atob(val)
|
|
28
|
+
if (isEmail(decoded)) {
|
|
29
|
+
setEmail(decoded)
|
|
30
|
+
// remove the hash
|
|
31
|
+
// isn't there a better way
|
|
32
|
+
history.pushState(
|
|
33
|
+
"",
|
|
34
|
+
document.title,
|
|
35
|
+
window.location.pathname + window.location.search
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, [hash])
|
|
40
|
+
|
|
41
|
+
const onSubmit = async(e) => {
|
|
42
|
+
e.preventDefault()
|
|
43
|
+
setIsLoading(true)
|
|
44
|
+
const res = await post("/api/v1/auth/reset_password", {email})
|
|
45
|
+
setIsLoading(false)
|
|
46
|
+
|
|
47
|
+
if (res.status === "ok") {
|
|
48
|
+
setErrors(null)
|
|
49
|
+
setIsSuccess(true)
|
|
50
|
+
} else {
|
|
51
|
+
setErrors(res.errors)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const onChangeEmail = (e) => setEmail(e.target.value)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div id="forgot-password-wrapper">
|
|
59
|
+
<div className="form-forgot text-center px-4 py-4 shadow-lg">
|
|
60
|
+
<div>
|
|
61
|
+
<div className="d-flex align-items-center justify-content-center">
|
|
62
|
+
{logo}
|
|
63
|
+
<span className="ms-1 fs-4">{name}</span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<hr />
|
|
67
|
+
|
|
68
|
+
<h1 className="h4 mt-3 mb-3 fw-normal">Reset your password</h1>
|
|
69
|
+
|
|
70
|
+
{isSuccess && (
|
|
71
|
+
<p className="text-success">
|
|
72
|
+
Success. If there is an account associated with this email, you will receive a password reset link in your inbox.
|
|
73
|
+
</p>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<form onSubmit={onSubmit}>
|
|
77
|
+
|
|
78
|
+
{errors?.form && (
|
|
79
|
+
<p className="text-danger">{errors.form}</p>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<div className="form-floating mb-4">
|
|
83
|
+
<input type="email"
|
|
84
|
+
disabled={isSuccess}
|
|
85
|
+
className="form-control"
|
|
86
|
+
id="input-email"
|
|
87
|
+
placeholder="name@example.com"
|
|
88
|
+
value={email}
|
|
89
|
+
onChange={onChangeEmail} />
|
|
90
|
+
<label htmlFor="input-email">Email Address</label>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
<button id={"btn-submit"}
|
|
95
|
+
type="submit"
|
|
96
|
+
disabled={isLoading || isSuccess}
|
|
97
|
+
className="w-100 btn btn-lg btn-primary"
|
|
98
|
+
onClick={onSubmit}>
|
|
99
|
+
<div className="d-flex flex-row align-items-center justify-content-center">
|
|
100
|
+
{isLoading && (
|
|
101
|
+
<div className="me-2"><ActivityIndicator color="#FFFFFF" /></div>
|
|
102
|
+
)}
|
|
103
|
+
Reset my Password
|
|
104
|
+
</div>
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
</form>
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<Footer name={name} />
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default ForgotPassword
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useState, useEffect} from "react"
|
|
3
|
+
import {useSearchParam} from "react-use"
|
|
4
|
+
import {ActivityIndicator} from "react-native"
|
|
5
|
+
import isEmail from "validator/lib/isEmail"
|
|
6
|
+
|
|
7
|
+
import post from "../../helpers/post"
|
|
8
|
+
|
|
9
|
+
import {set_is_signed_in} from "../index"
|
|
10
|
+
import Footer from "../Footer"
|
|
11
|
+
|
|
12
|
+
import "./set-new-password.scss"
|
|
13
|
+
|
|
14
|
+
const SetNewPassword = ({name, logo}) => {
|
|
15
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
16
|
+
const [error, setError] = useState()
|
|
17
|
+
const [isSuccess, setIsSuccess] = useState(false)
|
|
18
|
+
|
|
19
|
+
const [password1, setPassword1] = useState("")
|
|
20
|
+
const [password2, setPassword2] = useState("")
|
|
21
|
+
|
|
22
|
+
const user_id = useSearchParam("id")
|
|
23
|
+
const token = useSearchParam("token")
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
console.log("TOKEN", user_id, token)
|
|
27
|
+
}, [user_id, token])
|
|
28
|
+
|
|
29
|
+
const onSubmit = async(e) => {
|
|
30
|
+
e.preventDefault()
|
|
31
|
+
|
|
32
|
+
if (password1.length < 12) {
|
|
33
|
+
setError("Your password is too short")
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (password1 !== password2) {
|
|
38
|
+
setError("Passwords do not match")
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setIsLoading(true)
|
|
43
|
+
setError(null)
|
|
44
|
+
|
|
45
|
+
const password = password1
|
|
46
|
+
const res = await post("/api/v1/auth/set_new_password", {
|
|
47
|
+
user_id,
|
|
48
|
+
token,
|
|
49
|
+
password,
|
|
50
|
+
})
|
|
51
|
+
console.log("GOT RESSSS", res)
|
|
52
|
+
setIsLoading(false)
|
|
53
|
+
|
|
54
|
+
if (res.status === "ok") {
|
|
55
|
+
setIsSuccess(true)
|
|
56
|
+
} else {
|
|
57
|
+
setError(res.message)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const onChangePassword1 = (e) => setPassword1(e.target.value)
|
|
62
|
+
const onChangePassword2 = (e) => setPassword2(e.target.value)
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div id="set-new-password-wrapper">
|
|
66
|
+
<div className="form-set-new-password text-center px-4 py-4 shadow-lg">
|
|
67
|
+
<div>
|
|
68
|
+
<div className="d-flex align-items-center justify-content-center">
|
|
69
|
+
{logo}
|
|
70
|
+
<span className="ms-1 fs-4">{name}</span>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<hr />
|
|
74
|
+
|
|
75
|
+
<h1 className="h4 mt-3 mb-3 fw-normal">New Password</h1>
|
|
76
|
+
<p className="text-start text-muted">
|
|
77
|
+
Passwords must use at least 12 characters.
|
|
78
|
+
</p>
|
|
79
|
+
|
|
80
|
+
{error && (
|
|
81
|
+
<p className="text-start text-danger">{error}</p>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
{isSuccess && (
|
|
85
|
+
<p className="text-start text-success">
|
|
86
|
+
Your new password has successfully been set, you may now <a href="/signin">sign in</a> to your account
|
|
87
|
+
</p>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
<form onSubmit={onSubmit}>
|
|
92
|
+
|
|
93
|
+
<div className="form-floating">
|
|
94
|
+
<input type="password"
|
|
95
|
+
className="form-control"
|
|
96
|
+
id="input-password1"
|
|
97
|
+
placeholder="Password"
|
|
98
|
+
disabled={isLoading || isSuccess}
|
|
99
|
+
value={password1}
|
|
100
|
+
onChange={onChangePassword1} />
|
|
101
|
+
<label htmlFor="input-password1">Password</label>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="form-floating">
|
|
105
|
+
<input type="password"
|
|
106
|
+
className="form-control"
|
|
107
|
+
id="input-password2"
|
|
108
|
+
placeholder="Confirm Password"
|
|
109
|
+
disabled={isLoading || isSuccess}
|
|
110
|
+
value={password2}
|
|
111
|
+
onChange={onChangePassword2} />
|
|
112
|
+
<label htmlFor="input-password2">Confirm Password</label>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<button id={"btn-submit"}
|
|
116
|
+
type="submit"
|
|
117
|
+
disabled={isLoading || isSuccess}
|
|
118
|
+
className="w-100 btn btn-lg btn-primary mt-4"
|
|
119
|
+
onClick={onSubmit}>
|
|
120
|
+
<div className="d-flex flex-row align-items-center justify-content-center">
|
|
121
|
+
{isLoading && (
|
|
122
|
+
<div className="me-2"><ActivityIndicator color="#FFFFFF" /></div>
|
|
123
|
+
)}
|
|
124
|
+
Set new Password
|
|
125
|
+
</div>
|
|
126
|
+
</button>
|
|
127
|
+
|
|
128
|
+
</form>
|
|
129
|
+
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<Footer name={name} />
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default SetNewPassword
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#set-new-password-wrapper {
|
|
4
|
+
height: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-top: 40px;
|
|
9
|
+
padding-bottom: 40px;
|
|
10
|
+
// background-color: var(--bs-secondary);
|
|
11
|
+
background-color: $gray-500;
|
|
12
|
+
|
|
13
|
+
hr {
|
|
14
|
+
background-color: $gray-500;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
footer {
|
|
18
|
+
color: $light !important;
|
|
19
|
+
border-top: none !important;
|
|
20
|
+
padding-top: 0 !important;
|
|
21
|
+
margin-bottom: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.form-set-new-password {
|
|
25
|
+
width: 100%;
|
|
26
|
+
max-width: 380px;
|
|
27
|
+
margin: auto;
|
|
28
|
+
background-color: $light;
|
|
29
|
+
border-radius: 22px;
|
|
30
|
+
border: 1px solid $gray-600;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.form-set-new-password .form-floating:focus-within {
|
|
34
|
+
z-index: 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.form-set-new-password #input-password1 {
|
|
38
|
+
margin-bottom: -1px;
|
|
39
|
+
border-bottom-right-radius: 0;
|
|
40
|
+
border-bottom-left-radius: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.form-set-new-password #input-password2 {
|
|
44
|
+
border-top-left-radius: 0;
|
|
45
|
+
border-top-right-radius: 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useState, useEffect} from "react"
|
|
3
|
+
import {useSearchParam} from "react-use"
|
|
4
|
+
import isEmail from "validator/lib/isEmail"
|
|
5
|
+
|
|
6
|
+
import post from "../../helpers/post"
|
|
7
|
+
|
|
8
|
+
import {set_is_signed_in} from "../index"
|
|
9
|
+
import Footer from "../Footer"
|
|
10
|
+
|
|
11
|
+
import "./sign-in.scss"
|
|
12
|
+
|
|
13
|
+
const SignIn = ({name, logo}) => {
|
|
14
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
15
|
+
const [errors, setErrors] = useState()
|
|
16
|
+
|
|
17
|
+
const [email, setEmail] = useState("")
|
|
18
|
+
const [password, setPassword] = useState("")
|
|
19
|
+
const [forgotUrlParam, setForgotUrlParam] = useState(null)
|
|
20
|
+
|
|
21
|
+
const redirect = useSearchParam("redirect")
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
console.log("email changed", email)
|
|
25
|
+
if (isEmail(email)) {
|
|
26
|
+
setForgotUrlParam(btoa(email))
|
|
27
|
+
} else {
|
|
28
|
+
setForgotUrlParam("")
|
|
29
|
+
}
|
|
30
|
+
}, [email])
|
|
31
|
+
|
|
32
|
+
const onSubmit = async(e) => {
|
|
33
|
+
e.preventDefault()
|
|
34
|
+
setIsLoading(true)
|
|
35
|
+
const res = await post("/api/v1/auth/sign_in", {email, password})
|
|
36
|
+
setIsLoading(false)
|
|
37
|
+
|
|
38
|
+
if (res.status === "ok") {
|
|
39
|
+
set_is_signed_in(true)
|
|
40
|
+
if (redirect) {
|
|
41
|
+
window.location = redirect
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
window.location = "/workspaces"
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
setErrors(res.errors)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onChangeEmail = (e) => {
|
|
52
|
+
setEmail(e.target.value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const onChangePassword = (e) => {
|
|
56
|
+
setPassword(e.target.value)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div id="sign-in-wrapper">
|
|
61
|
+
<div className="form-signin text-center px-4 py-4 shadow-lg">
|
|
62
|
+
<div>
|
|
63
|
+
<div className="d-flex align-items-center justify-content-center">
|
|
64
|
+
{logo}
|
|
65
|
+
<span className="ms-1 fs-4">{name}</span>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<hr />
|
|
69
|
+
|
|
70
|
+
<h1 className="h4 mt-3 mb-3 fw-normal">Sign In</h1>
|
|
71
|
+
<p className="text-start text-muted">
|
|
72
|
+
Use your email and password to sign in. Don't have an account ?
|
|
73
|
+
<a href="/signup" className="ms-1">Sign up here</a>
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<form onSubmit={onSubmit}>
|
|
77
|
+
|
|
78
|
+
{errors?.form && (
|
|
79
|
+
<p className="text-danger">{errors.form}</p>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<div className="form-floating">
|
|
83
|
+
<input type="email"
|
|
84
|
+
className="form-control"
|
|
85
|
+
id="input-email"
|
|
86
|
+
placeholder="name@example.com"
|
|
87
|
+
value={email}
|
|
88
|
+
onChange={onChangeEmail} />
|
|
89
|
+
<label htmlFor="input-email">Email Address</label>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="form-floating">
|
|
93
|
+
<input type="password"
|
|
94
|
+
className="form-control"
|
|
95
|
+
id="input-password"
|
|
96
|
+
placeholder="Password"
|
|
97
|
+
value={password}
|
|
98
|
+
onChange={onChangePassword} />
|
|
99
|
+
<label htmlFor="input-password">Password</label>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className="checkbox mt-3 mb-3 text-start">
|
|
103
|
+
<label style={{cursor: "pointer", userSelect: "none"}}>
|
|
104
|
+
<input
|
|
105
|
+
type="checkbox"
|
|
106
|
+
value="remember-me"
|
|
107
|
+
defaultChecked /> Remember me
|
|
108
|
+
</label>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<button className="w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign In</button>
|
|
112
|
+
|
|
113
|
+
<div className="mt-3 text-start w-100">
|
|
114
|
+
<a href={`/forgot-password${forgotUrlParam && "#" + forgotUrlParam}`}>Forgot your password?</a>
|
|
115
|
+
</div>
|
|
116
|
+
</form>
|
|
117
|
+
|
|
118
|
+
<hr />
|
|
119
|
+
<p className="text-muted">OR</p>
|
|
120
|
+
|
|
121
|
+
<a href="/signup"
|
|
122
|
+
className="sign-in-github-btn p-2 btn-lg w-100"
|
|
123
|
+
style={{}}>
|
|
124
|
+
<div style={{}} className="me-2">
|
|
125
|
+
<svg height="28px" width="28px" viewBox="0 0 16 16" style={{fill: "currentColor"}}>
|
|
126
|
+
<path fillRule={"evenodd"} d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
|
|
127
|
+
0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01
|
|
128
|
+
1.08.58 1.23.82.72 1.21 1.87.87
|
|
129
|
+
2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12
|
|
130
|
+
0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08
|
|
131
|
+
2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0
|
|
132
|
+
.21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
|
133
|
+
</svg>
|
|
134
|
+
</div>
|
|
135
|
+
<div className="">
|
|
136
|
+
Sign in with GitHub
|
|
137
|
+
</div>
|
|
138
|
+
</a>
|
|
139
|
+
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<Footer name={name} />
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default SignIn
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#sign-in-wrapper {
|
|
4
|
+
height: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-top: 40px;
|
|
9
|
+
padding-bottom: 40px;
|
|
10
|
+
// background-color: var(--bs-secondary);
|
|
11
|
+
background-color: $gray-500;
|
|
12
|
+
|
|
13
|
+
hr {
|
|
14
|
+
background-color: $gray-500;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
footer {
|
|
18
|
+
color: $light !important;
|
|
19
|
+
border-top: none !important;
|
|
20
|
+
padding-top: 0 !important;
|
|
21
|
+
margin-bottom: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
.form-signin {
|
|
26
|
+
width: 100%;
|
|
27
|
+
max-width: 380px;
|
|
28
|
+
margin: auto;
|
|
29
|
+
background-color: $light;
|
|
30
|
+
border-radius: 22px;
|
|
31
|
+
border: 1px solid $gray-600;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.form-signin .checkbox {
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.form-signin .form-floating:focus-within {
|
|
39
|
+
z-index: 2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.form-signin input[type="email"] {
|
|
43
|
+
margin-bottom: -1px;
|
|
44
|
+
border-bottom-right-radius: 0;
|
|
45
|
+
border-bottom-left-radius: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.form-signin #input-password {
|
|
49
|
+
margin-bottom: 10px;
|
|
50
|
+
border-top-left-radius: 0;
|
|
51
|
+
border-top-right-radius: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.sign-in-github-btn {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
background-color: #24292E;
|
|
58
|
+
font-size: 1.25rem;
|
|
59
|
+
color: $gray-200;
|
|
60
|
+
text-decoration: none;
|
|
61
|
+
|
|
62
|
+
&:hover {
|
|
63
|
+
color: $white;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useState, useEffect} from "react"
|
|
3
|
+
import {ActivityIndicator} from "react-native"
|
|
4
|
+
import page from "page"
|
|
5
|
+
|
|
6
|
+
import post from "../../helpers/post"
|
|
7
|
+
import {set_is_signed_in} from "../index"
|
|
8
|
+
|
|
9
|
+
const SignOut = () => {
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
setTimeout(async() => {
|
|
13
|
+
const res = await post("/api/v1/auth/sign_out")
|
|
14
|
+
if (res.status === "ok") {
|
|
15
|
+
set_is_signed_in(false)
|
|
16
|
+
localStorage.clear()
|
|
17
|
+
page.redirect("/")
|
|
18
|
+
} else {
|
|
19
|
+
throw new Error("unable to sign out")
|
|
20
|
+
}
|
|
21
|
+
}, 60)
|
|
22
|
+
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div id="sign-out-wrapper">
|
|
27
|
+
<div className="mt-3 d-flex justify-content-center align-items-center">
|
|
28
|
+
<div className="me-2"><ActivityIndicator /></div>
|
|
29
|
+
<div className="">Signing out, you will be redirected shortly</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default SignOut
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {useState} from "react"
|
|
3
|
+
|
|
4
|
+
import post from "../../helpers/post"
|
|
5
|
+
import Footer from "../Footer"
|
|
6
|
+
|
|
7
|
+
import "./sign-up.scss"
|
|
8
|
+
|
|
9
|
+
const SignUp = ({logo, name}) => {
|
|
10
|
+
const [email, setEmail] = useState("")
|
|
11
|
+
const [password, setPassword] = useState("")
|
|
12
|
+
const [passwordConfirm, setPasswordConfirm] = useState("")
|
|
13
|
+
const [error, setError] = useState(null)
|
|
14
|
+
|
|
15
|
+
const onSubmit = async() => {
|
|
16
|
+
setError(null)
|
|
17
|
+
if (!password || password !== passwordConfirm) {
|
|
18
|
+
setError("Passwords do not match")
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
const res = await post("/api/v1/auth/sign_up", {email, password})
|
|
22
|
+
if (res.status === "ok") {
|
|
23
|
+
window.location = "/workspaces"
|
|
24
|
+
} else if (res.status === "error") {
|
|
25
|
+
setError(res.message)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const onChangeEmail = (e) => {
|
|
30
|
+
setEmail(e.target.value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const onChangePassword = (e) => {
|
|
34
|
+
setPassword(e.target.value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const onChangePasswordConfirm = (e) => {
|
|
38
|
+
setPasswordConfirm(e.target.value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div id="sign-up-wrapper">
|
|
44
|
+
<div className="form-signup text-center px-4 py-4 shadow-lg">
|
|
45
|
+
<div>
|
|
46
|
+
<div className="d-flex align-items-center justify-content-center">
|
|
47
|
+
{logo}
|
|
48
|
+
<span className="ms-1 fs-4">{name}</span>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<hr />
|
|
52
|
+
|
|
53
|
+
<h1 className="h4 mt-3 mb-3 fw-normal">Sign Up</h1>
|
|
54
|
+
<p className="text-muted">
|
|
55
|
+
Already have an account ? <a href="/signin" className="ms-1">Sign in here</a>
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
{error && (
|
|
59
|
+
<div className="invalid-feedback d-flex text-start">
|
|
60
|
+
{error}
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
<div className="form-floating">
|
|
65
|
+
<input type="email"
|
|
66
|
+
className="form-control"
|
|
67
|
+
id="input-email"
|
|
68
|
+
placeholder="name@example.com"
|
|
69
|
+
value={email}
|
|
70
|
+
onChange={onChangeEmail} />
|
|
71
|
+
<label htmlFor="input-email">Email Address</label>
|
|
72
|
+
</div>
|
|
73
|
+
<div className="form-floating">
|
|
74
|
+
<input type="password"
|
|
75
|
+
className="form-control"
|
|
76
|
+
id="input-password"
|
|
77
|
+
placeholder="Password"
|
|
78
|
+
value={password}
|
|
79
|
+
onChange={onChangePassword} />
|
|
80
|
+
<label htmlFor="input-password">Password</label>
|
|
81
|
+
</div>
|
|
82
|
+
<div className="form-floating">
|
|
83
|
+
<input type="password"
|
|
84
|
+
className="form-control"
|
|
85
|
+
id="input-password-confirm"
|
|
86
|
+
placeholder="Confirm Password"
|
|
87
|
+
value={passwordConfirm}
|
|
88
|
+
onChange={onChangePasswordConfirm} />
|
|
89
|
+
<label htmlFor="input-password-confirm">Confirm Password</label>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="checkbox mt-3 mb-3 text-start">
|
|
93
|
+
<label style={{cursor: "pointer", userSelect: "none"}}>
|
|
94
|
+
<input type="checkbox" value="terms-accepted" /> I agree to the terms and privacy policy.
|
|
95
|
+
</label>
|
|
96
|
+
</div>
|
|
97
|
+
<button className="w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign Up</button>
|
|
98
|
+
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<Footer name={name} />
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default SignUp
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
#sign-up-wrapper {
|
|
4
|
+
height: 100%;
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: center;
|
|
8
|
+
padding-top: 40px;
|
|
9
|
+
padding-bottom: 40px;
|
|
10
|
+
background-color: $gray-500;
|
|
11
|
+
|
|
12
|
+
hr {
|
|
13
|
+
background-color: $gray-500;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
footer {
|
|
17
|
+
color: $light !important;
|
|
18
|
+
border-top: none !important;
|
|
19
|
+
padding-top: 0 !important;
|
|
20
|
+
margin-bottom: 0 !important;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.form-signup {
|
|
24
|
+
width: 100%;
|
|
25
|
+
max-width: 380px;
|
|
26
|
+
margin: auto;
|
|
27
|
+
background-color: $light;
|
|
28
|
+
border-radius: 22px;
|
|
29
|
+
border: 1px solid $gray-600;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.form-signup .checkbox {
|
|
33
|
+
font-weight: 400;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.form-signup .form-floating:focus-within {
|
|
37
|
+
z-index: 2;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.form-signup input[type="email"] {
|
|
41
|
+
margin-bottom: -1px;
|
|
42
|
+
border-bottom-right-radius: 0;
|
|
43
|
+
border-bottom-left-radius: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.form-signup #input-password {
|
|
47
|
+
border-radius: 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.form-signup #input-password-confirm {
|
|
51
|
+
margin-bottom: 10px;
|
|
52
|
+
border-top-left-radius: 0;
|
|
53
|
+
border-top-right-radius: 0;
|
|
54
|
+
border-top: none;
|
|
55
|
+
}
|
|
56
|
+
}
|
package/auth/index.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import page from "page"
|
|
3
|
+
|
|
4
|
+
import SignIn from "./SignIn"
|
|
5
|
+
import SignUp from "./SignUp"
|
|
6
|
+
import SignOut from "./SignOut"
|
|
7
|
+
import ForgotPassword from "./ForgotPassword"
|
|
8
|
+
import SetNewPassword from "./SetNewPassword"
|
|
9
|
+
|
|
10
|
+
import post from "../helpers/post"
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
SignIn,
|
|
14
|
+
SignUp,
|
|
15
|
+
SignOut,
|
|
16
|
+
ForgotPassword,
|
|
17
|
+
SetNewPassword,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const UID_STORAGE_KEY = "rb::session-user_id"
|
|
21
|
+
|
|
22
|
+
let __is_authenticated = null
|
|
23
|
+
let __user_id = localStorage.getItem(UID_STORAGE_KEY)
|
|
24
|
+
|
|
25
|
+
const run_session_check = async() => {
|
|
26
|
+
const res = await post("/api/v1/auth/check_session")
|
|
27
|
+
const {is_signed_in, user_id} = res
|
|
28
|
+
__is_authenticated = is_signed_in
|
|
29
|
+
__user_id = user_id
|
|
30
|
+
// cache user_id in localStorage
|
|
31
|
+
if (user_id) {
|
|
32
|
+
localStorage.setItem(UID_STORAGE_KEY, user_id)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// manually run first check
|
|
37
|
+
// TODO: this should come from localStorage
|
|
38
|
+
const redirect_sign_in = (ctx) => {
|
|
39
|
+
const redirect_path = ctx.canonicalPath
|
|
40
|
+
page.redirect(`/signin?redirect=${encodeURIComponent(redirect_path)}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export const session_restrict = (ctx, next) => {
|
|
45
|
+
if (typeof __is_authenticated !== "boolean") {
|
|
46
|
+
run_session_check()
|
|
47
|
+
.then(() => {
|
|
48
|
+
if (__is_authenticated) {
|
|
49
|
+
next()
|
|
50
|
+
} else {
|
|
51
|
+
redirect_sign_in(ctx)
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.catch((err) => {
|
|
55
|
+
console.log("warning error in check session request", err)
|
|
56
|
+
throw err
|
|
57
|
+
})
|
|
58
|
+
} else if (__is_authenticated) {
|
|
59
|
+
next()
|
|
60
|
+
} else {
|
|
61
|
+
console.log("AUTH", JSON.stringify(__is_authenticated))
|
|
62
|
+
console.log("NOT AUTHENTICATED, REDIRECT HERE")
|
|
63
|
+
next()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export const get_uid = () => __user_id
|
|
69
|
+
|
|
70
|
+
export const set_is_signed_in = (val) => __is_authenticated = val
|
|
71
|
+
|
|
72
|
+
export const is_signed_in = () => __is_authenticated
|
package/helpers/post.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import axios from "axios"
|
|
3
|
+
|
|
4
|
+
import config from "config"
|
|
5
|
+
|
|
6
|
+
const {BASE_URL} = config
|
|
7
|
+
|
|
8
|
+
const client = axios.create({
|
|
9
|
+
withCredentials: true,
|
|
10
|
+
headers: {
|
|
11
|
+
"Content-Type": "application/json"
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const post = async(url, payload) => {
|
|
16
|
+
const res = await client.post(`${BASE_URL}${url}`, payload)
|
|
17
|
+
return res.data
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default post
|
package/package.json
CHANGED
package/rpc_post.js
CHANGED