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 }