Register account
Seto Elkahfi committed
Aug 1, 2024 at 15:46 UTC
3eca43bfb77fd1d031cb996dbb33640bb2f521b2
6 files changed
+220
-91
backend/musik88-web/app/controllers/api/v1/user_controller.rb
+1
-1
@@ -38,7 +38,7 @@ module Api
38
39
if @user.save
40
# Deliver the signup email.
41
- UserNotifierMailer.send_signup_email(user).deliver
41
+ UserNotifierMailer.send_signup_email(@user).deliver
42
render_success
43
else
44
# Get Devise proper error response.
backend/musik88-web/config/routes/api.rb
+2
-2
@@ -5,6 +5,7 @@ namespace :api do
5
root to: 'welcome#index'
6
7
devise_scope :user do
8
+ post '/register' => 'user#register'
9
post '/login' => 'user#login'
10
delete '/logout' => 'user#logout'
11
get '/splitfire/:id' => 'audio_file#splitfire_detail'
@@ -21,8 +22,7 @@ namespace :api do
22
post '/bass_backing_track', to: 'audio_file#bass_backing_track'
23
post '/drum_backing_track', to: 'audio_file#drum_backing_track'
24
24
- post '/register', to: 'user#register'
25
- get '/community/:exclude', to: 'user#community'
25
+ get '/community/:exclude', to: 'user#community'
26
27
get '/lyric_synches/:audio_file_id', to: 'karaoke#lyric_synches'
28
post '/lyric_synches/:audio_file_id', to: 'karaoke#lyric_synches_post'
frontend/splitfire-desktop/app/register/page.tsx
+171
-79
@@ -1,97 +1,189 @@
1
-'use client';
1
+"use client";
2
3
import { invoke } from "@tauri-apps/api";
4
import { useState } from "react";
5
import * as Form from "@radix-ui/react-form";
6
-import { TAURI_ACCOUNT_REGISTER } from "../_src/lib/tauriHandler";
6
+import { TAURI_ACCOUNT_REGISTER, TauriResponse } from "../_src/lib/tauriHandler";
7
import { Button } from "../_ui/components/button";
8
+import { useLogger } from "../_src/lib/logger";
9
+import { AccountRegisterResponse } from "@/models/account";
10
+
11
+enum State {
12
+ LOADING,
13
+ LOADED,
14
+ ERROR,
15
+ SUCCESS
16
+}
17
+
18
+enum SignupState {
19
+ ERROR,
20
+ SUCCESS
21
+}
22
+
23
+type SignupFormPayload = {
24
+ name: string,
25
+ email: string,
26
+ password: string
27
+}
28
29
export default function Page() {
30
+ const log = useLogger("Register/Page");
31
+ const [state, setState] = useState(State.LOADED);
32
33
+ const registerCallback = (state: SignupState) => {
34
+ log.debug("Register callback", state);
35
+ if (state === SignupState.SUCCESS) {
36
+ setState(State.SUCCESS);
37
+ } else {
38
+ setState(State.ERROR);
39
+ }
40
+ }
41
+ const view = state === State.SUCCESS ? <SignupSuccess /> : <SignupForm registerCallback={registerCallback} />;
42
+ return (
43
+ <div className="prose prose-sm prose-invert max-w-none">
44
+ <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
45
+ {view}
46
+ </div>
47
+ </div>
48
+ );
49
+}
50
+
51
+function SignupSuccess() {
52
+ return (
53
+ <div>
54
+ <h1>Success</h1>
55
+ <p>Your account has been created successfully.</p>
56
+ <p>Check your email to confirm your account.</p>
57
+ </div>
58
+ );
59
+}
60
+
61
+function SignupForm({ registerCallback }: { registerCallback: (state: SignupState) => void }) {
62
+ const log = useLogger("Register/Form");
63
const [name, setName] = useState("");
64
const [email, setEmail] = useState("");
65
const [password, setPassword] = useState("");
66
+ const [state, setState] = useState(State.LOADED);
67
68
const register = async () => {
16
- const payload = {
17
- name: name,
18
- email: email,
19
- password: password,
20
- };
21
- console.log("payload", payload);
22
- const response = await invoke(TAURI_ACCOUNT_REGISTER, payload);
23
- console.log("response", response);
69
+ setState(State.LOADING);
70
+ try {
71
+ const payload: SignupFormPayload = {
72
+ name,
73
+ email,
74
+ password
75
+ };
76
+ log.debug("payload", payload);
77
+ const response = await invoke<AccountRegisterResponse>(TAURI_ACCOUNT_REGISTER, payload);
78
+ log.debug("response", response);
79
+ if (response.status === TauriResponse.ERROR) {
80
+ log.error("Register failed", response);
81
+ setState(State.ERROR);
82
+ return;
83
+ }
84
+ setState(State.SUCCESS);
85
+ // Call the callback to update the parent state
86
+ registerCallback(SignupState.SUCCESS);
87
+ } catch (error) {
88
+ log.error("Register failed", error);
89
+ setState(State.ERROR);
90
+ }
91
};
92
93
+ const buttonText = state === State.LOADING ? "Registering..." : "Register";
94
+
95
return (
27
- <div className="prose prose-sm prose-invert max-w-none">
28
- <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
29
- <Form.Root className="FormRoot" onSubmit={(event) => { event.preventDefault()}}>
30
- <Form.Field className="FormField" name="name">
31
- <div
32
- style={{
33
- display: "flex",
34
- alignItems: "baseline",
35
- justifyContent: "space-between",
36
- }}
37
- >
38
- <Form.Label className="FormLabel">Name</Form.Label>
39
- <Form.Message className="FormMessage" match="valueMissing">
40
- Please enter your name
41
- </Form.Message>
42
- <Form.Message className="FormMessage" match="typeMismatch">
43
- Please provide a valid name
44
- </Form.Message>
45
- </div>
46
- <Form.Control asChild>
47
- <input className="Input" type="text" value={name} onChange={(e) => setName(e.currentTarget.value)} required />
48
- </Form.Control>
49
- </Form.Field>
50
- <Form.Field className="FormField" name="email">
51
- <div
52
- style={{
53
- display: "flex",
54
- alignItems: "baseline",
55
- justifyContent: "space-between",
56
- }}
57
- >
58
- <Form.Label className="FormLabel">Email</Form.Label>
59
- <Form.Message className="FormMessage" match="valueMissing">
60
- Please enter your email
61
- </Form.Message>
62
- <Form.Message className="FormMessage" match="typeMismatch">
63
- Please provide a valid email
64
- </Form.Message>
65
- </div>
66
- <Form.Control asChild>
67
- <input className="Input" type="email" value={email} onChange={(e) => setEmail(e.currentTarget.value)} required />
68
- </Form.Control>
69
- </Form.Field>
70
- <Form.Field className="FormField" name="password">
71
- <div
72
- style={{
73
- display: "flex",
74
- alignItems: "baseline",
75
- justifyContent: "space-between",
76
- }}
77
- >
78
- <Form.Label className="FormLabel">Password</Form.Label>
79
- <Form.Message className="FormMessage" match="valueMissing">
80
- Please enter your email
81
- </Form.Message>
82
- <Form.Message className="FormMessage" match="typeMismatch">
83
- Please provide a valid email
84
- </Form.Message>
85
- </div>
86
- <Form.Control asChild>
87
- <input className="Input" type="password" value={password} onChange={(e)=>{setPassword(e.currentTarget.value)}} required />
88
- </Form.Control>
89
- </Form.Field>
90
- <Form.Field className="FormField" name="button">
91
- <Button onClick={register} variant={"outline"} size={"lg"}>Register</Button>
92
- </Form.Field>
93
- </Form.Root>
96
+ <Form.Root
97
+ className="FormRoot"
98
+ onSubmit={(event) => {
99
+ event.preventDefault();
100
+ }}
101
+ >
102
+ <Form.Field className="FormField" name="name">
103
+ <div
104
+ style={{
105
+ display: "flex",
106
+ alignItems: "baseline",
107
+ justifyContent: "space-between",
108
+ }}
109
+ >
110
+ <Form.Label className="FormLabel">Name</Form.Label>
111
+ <Form.Message className="FormMessage" match="valueMissing">
112
+ Please enter your name
113
+ </Form.Message>
114
+ <Form.Message className="FormMessage" match="typeMismatch">
115
+ Please provide a valid name
116
+ </Form.Message>
117
</div>
95
- </div>
96
- );
118
+ <Form.Control asChild>
119
+ <input
120
+ className="Input"
121
+ type="text"
122
+ value={name}
123
+ onChange={(e) => setName(e.currentTarget.value)}
124
+ required
125
+ />
126
+ </Form.Control>
127
+ </Form.Field>
128
+ <Form.Field className="FormField" name="email">
129
+ <div
130
+ style={{
131
+ display: "flex",
132
+ alignItems: "baseline",
133
+ justifyContent: "space-between",
134
+ }}
135
+ >
136
+ <Form.Label className="FormLabel">Email</Form.Label>
137
+ <Form.Message className="FormMessage" match="valueMissing">
138
+ Please enter your email
139
+ </Form.Message>
140
+ <Form.Message className="FormMessage" match="typeMismatch">
141
+ Please provide a valid email
142
+ </Form.Message>
143
+ </div>
144
+ <Form.Control asChild>
145
+ <input
146
+ className="Input"
147
+ type="email"
148
+ value={email}
149
+ onChange={(e) => setEmail(e.currentTarget.value)}
150
+ required
151
+ />
152
+ </Form.Control>
153
+ </Form.Field>
154
+ <Form.Field className="FormField" name="password">
155
+ <div
156
+ style={{
157
+ display: "flex",
158
+ alignItems: "baseline",
159
+ justifyContent: "space-between",
160
+ }}
161
+ >
162
+ <Form.Label className="FormLabel">Password</Form.Label>
163
+ <Form.Message className="FormMessage" match="valueMissing">
164
+ Please enter your email
165
+ </Form.Message>
166
+ <Form.Message className="FormMessage" match="typeMismatch">
167
+ Please provide a valid email
168
+ </Form.Message>
169
+ </div>
170
+ <Form.Control asChild>
171
+ <input
172
+ className="Input"
173
+ type="password"
174
+ value={password}
175
+ onChange={(e) => {
176
+ setPassword(e.currentTarget.value);
177
+ }}
178
+ required
179
+ />
180
+ </Form.Control>
181
+ </Form.Field>
182
+ <Form.Field className="FormField" name="button">
183
+ <Button onClick={register} variant={"outline"} size={"lg"}>
184
+ {buttonText}
185
+ </Button>
186
+ </Form.Field>
187
+ </Form.Root>
188
+ )
189
}
\ No newline at end of file
frontend/splitfire-desktop/models/account.ts
+6
@@ -7,3 +7,9 @@ export interface AccountLoginResponse {
7
access_token: string | null,
8
user: User | null
9
}
10
+
11
+export interface AccountRegisterResponse {
12
+ status: TauriResponse,
13
+ message: string,
14
+ user: User | null
15
+}
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/models/account.rs
+16
@@ -28,4 +28,20 @@ pub struct User {
28
pub followers_count: i32,
29
pub following_count: i32,
30
pub about: String
31
+}
32
+
33
+#[derive(Serialize)]
34
+#[derive(Debug)]
35
+pub struct AccountRegisterResponse {
36
+ pub status: TauriResponse,
37
+ pub message: String,
38
+ pub user: Option<User>,
39
+}
40
+
41
+#[derive(Serialize, Deserialize)]
42
+#[derive(Debug)]
43
+pub struct RegisterResponse {
44
+ pub code: i32,
45
+ pub message: String,
46
+ pub user: Option<User>,
47
}
\ No newline at end of file
frontend/splitfire-desktop/src-tauri/src/rest/account.rs
+24
-9
@@ -3,7 +3,7 @@ use crate::{
3
base_url_builder, PATH_ACCOUNT_LOGIN, PATH_ACCOUNT_LOGOUT, PATH_ACCOUNT_REGISTER,
4
},
5
models::{
6
- account::{AccountLoginResponse, LoginResponse},
6
+ account::{AccountLoginResponse, AccountRegisterResponse, LoginResponse, RegisterResponse},
7
player::TauriResponse,
8
},
9
};
@@ -112,14 +112,15 @@ pub async fn account_logout(access_token: String) {
112
}
113
114
#[tauri::command]
115
-pub async fn account_register(username: String, email: String, password: String) {
116
- debug!(
117
- "Registering with username {} and email {}.",
118
- username, email
119
- );
115
+pub async fn account_register(
116
+ name: String,
117
+ email: String,
118
+ password: String,
119
+) -> AccountRegisterResponse {
120
+ debug!("Registering with name {} and email {}.", name, email);
121
// Register
122
let body = json!( {
122
- "username": username,
123
+ "name": name,
124
"email": email,
125
"password": password
126
});
@@ -132,17 +133,31 @@ pub async fn account_register(username: String, email: String, password: String)
133
match response {
134
Ok(response) => {
135
debug!("Got response: {:?}", response);
135
- let res: LoginResponse = match response.json().await {
136
+ let res: RegisterResponse = match response.json().await {
137
Ok(json) => json,
138
Err(e) => {
139
println!("Failed to parse response: {:?}", e);
139
- return;
140
+ return AccountRegisterResponse {
141
+ status: TauriResponse::Error,
142
+ message: e.to_string(),
143
+ user: None,
144
+ };
145
}
146
};
147
debug!("Register response: {:?}", res);
148
+ AccountRegisterResponse {
149
+ status: TauriResponse::Success,
150
+ message: res.message,
151
+ user: res.user,
152
+ }
153
}
154
Err(e) => {
155
println!("Failed to get response: {:?}", e);
156
+ AccountRegisterResponse {
157
+ status: TauriResponse::Error,
158
+ message: e.to_string(),
159
+ user: None,
160
+ }
161
}
162
}
163
}