wip

Seto Elkahfi committed Aug 4, 2024 at 17:25 UTC ea2068f3688ad8e182cf51466ff3f07fb1e0a81c
32 files changed +392 -357
frontend/splitfire-desktop/app/_ui/components/button.tsx
+2
@@ -1,3 +1,5 @@
1 +"use client";
2 +
3 import * as React from 'react'
4 import { Slot } from '@radix-ui/react-slot'
5 import { cva, type VariantProps } from 'class-variance-authority'
frontend/splitfire-desktop/app/_ui/global-nav.tsx
+1 -1
@@ -7,7 +7,7 @@ import { MenuAlt2Icon, XIcon } from '@heroicons/react/solid';
7 import clsx from 'clsx';
8 import { useContext, useState } from 'react';
9 import Image from 'next/image';
10 -import { UserContext } from '../../lib/CurrentUserContext';
10 +import { UserContext } from '../../lib/current-user-context';
11 import { useLogger } from '@/lib/logger';
12 import { PARAMS_USER_ID } from '@/app/_lib/params';
13
frontend/splitfire-desktop/app/components/index.tsx
+1 -1
@@ -1,6 +1,6 @@
1 "use client";
2
3 -import { TAURI_CONTENT_CAROUSEL } from "@/lib/tauriHandler";
3 +import { TAURI_CONTENT_CAROUSEL } from "@/lib/tauri-handler";
4 import { ContentCarouselResponse } from "@/models/content";
5 import { SongProvider } from "@/models/song-response";
6 import { invoke } from "@tauri-apps/api";
frontend/splitfire-desktop/app/debug/page.tsx
+4 -4
@@ -5,12 +5,12 @@ import { invoke } from "@tauri-apps/api/tauri";
5 import {
6 TAURI_GET_ENVIRONMENT,
7 TAURI_SET_ENVIRONMENT,
8 -} from "../../lib/tauriHandler";
9 -import { Button } from "../_ui/components/button";
8 +} from "@/lib/tauri-handler";
9 +import { Button } from "@/app/_ui/components/button";
10 import { useEffect, useState } from "react";
11 import { TauriResponse } from "@/models/shared";
12 -import { useLogger } from "../../lib/logger";
13 -import { LoadingView } from "../_ui/LoadingView";
12 +import { useLogger } from "@/lib/logger";
13 +import { LoadingView } from "@/app/_ui/LoadingView";
14
15 enum State {
16 LOADING,
frontend/splitfire-desktop/app/layout.tsx
+1 -1
@@ -5,7 +5,7 @@ import { GlobalNav } from "./_ui/global-nav";
5 import "./globals.css";
6 import { useEffect, useState } from "react";
7 import { CurrentUser, CurrentUserType, db } from "../lib/db";
8 -import { UserContext } from "../lib/CurrentUserContext";
8 +import { UserContext } from "../lib/current-user-context";
9 import { useLogger } from "../lib/logger";
10
11 enum State {
frontend/splitfire-desktop/app/login/page.tsx
+9 -7
@@ -2,16 +2,17 @@
2
3 import * as Form from "@radix-ui/react-form";
4 import { invoke } from "@tauri-apps/api/tauri";
5 -import { TAURI_ACCOUNT_LOGIN } from "@/lib/tauriHandler";
5 +import { TAURI_ACCOUNT_LOGIN } from "@/lib/tauri-handler";
6 import { Button } from "@/app/_ui/components/button";
7 import { useState } from "react";
8 -import { UserContext } from "@/lib/CurrentUserContext";
9 -import { CurrentUser, CurrentUserType, db } from "../../lib/db";
8 +import { UserContext } from "@/lib/current-user-context";
9 +import { CurrentUser, CurrentUserType, db } from "@/lib/db";
10 import { AccountLoginResponse } from "@/models/account";
11 import { useRouter } from "next/navigation";
12 import { TauriResponse } from "@/models/shared";
13 import { Mode } from "@/models/mode";
14 -import { LoadingView } from "../_ui/LoadingView";
14 +import { LoadingView } from "@/app/_ui/LoadingView";
15 +import { useLogger } from "@/lib/logger";
16
17 enum State {
18 LOADING,
@@ -20,6 +21,7 @@ enum State {
21
22 export default function Page() {
23
24 + const log = useLogger("Login/Page");
25 const router = useRouter();
26 const [state, setState] = useState(State.LOADED);
27 const [emailOrUsername, setEmailOrUsername] = useState("");
@@ -31,16 +33,16 @@ export default function Page() {
33 username: emailOrUsername,
34 password: password,
35 };
34 - console.log("payload", payload);
36 + log.debug("payload", payload);
37 const response: AccountLoginResponse = await invoke(TAURI_ACCOUNT_LOGIN, payload);
36 - console.log("response", response);
38 + log.debug("response", response);
39 if (response.status === TauriResponse.ERROR) {
40 console.error("Login failed", response);
41 return;
42 }
43 // Sanity check
44 if (response.user === null || response.access_token === null) {
43 - console.error("Login failed", response);
45 + log.error("Login failed", response);
46 return;
47 }
48
frontend/splitfire-desktop/app/logout/page.tsx
+2 -2
@@ -1,11 +1,11 @@
1 "use client";
2
3 import { useContext, useState } from "react";
4 -import { UserContext } from "../../lib/CurrentUserContext";
4 +import { UserContext } from "../../lib/current-user-context";
5 import { useLogger } from "../../lib/logger";
6 import { CurrentUser, db } from "../../lib/db";
7 import { invoke } from "@tauri-apps/api/tauri";
8 -import { TAURI_ACCOUNT_LOGOUT } from "../../lib/tauriHandler";
8 +import { TAURI_ACCOUNT_LOGOUT } from "../../lib/tauri-handler";
9 import { useRouter } from "next/navigation";
10 import { Button } from "../_ui/components/button";
11
frontend/splitfire-desktop/app/play/_components/audio-info-middle.tsx
+1 -1
@@ -1,4 +1,4 @@
1 -import { useCountDown } from "@/lib/useCountDown";
1 +import { useCountDown } from "@/lib/use-count-down";
2 import { useLogger } from "@/lib/logger";
3 import { useEffect } from "react";
4
frontend/splitfire-desktop/app/play/_components/player.tsx
+1 -1
@@ -9,7 +9,7 @@ import {
9 TAURI_PLAYER_RESUMED,
10 TAURI_PLAYER_SET_VOLUME,
11 TAURI_PLAYER_STOP,
12 -} from "@/lib/tauriHandler";
12 +} from "@/lib/tauri-handler";
13 import { invoke } from "@tauri-apps/api";
14 import { useState } from "react";
15 import { ControlButtons } from "./control-button";
frontend/splitfire-desktop/app/play/page.tsx
+2 -2
@@ -2,14 +2,14 @@
2
3 import { invoke } from "@tauri-apps/api";
4 import { useContext, useEffect, useState } from "react";
5 -import { TAURI_PLAYER_PREPARE } from "../../lib/tauriHandler";
5 +import { TAURI_PLAYER_PREPARE } from "../../lib/tauri-handler";
6 import { useRouter, useSearchParams } from "next/navigation";
7 import { useLogger } from "../../lib/logger";
8 import { IconSpinner } from "../_ui/components/icons";
9 import { PlayerPrepareResponse } from "@/models/content";
10 import { TauriResponse } from "@/models/shared";
11 import Player from "./_components/player";
12 -import { UserContext } from "../../lib/CurrentUserContext";
12 +import { UserContext } from "../../lib/current-user-context";
13
14 enum State {
15 LOADING,
frontend/splitfire-desktop/app/profile/_components/index.tsx deleted
-3
@@ -1,3 +0,0 @@
1 -
2 -
3 -export default function Index() {}
\ No newline at end of file
frontend/splitfire-desktop/app/profile/components/index.tsx new
+153
@@ -0,0 +1,153 @@
1 +"use client";
2 +
3 +import { useContext, useEffect, useState } from "react";
4 +import { useLogger } from "@/lib/logger";
5 +import User, { usernameOrId } from "@/models/user";
6 +import { useSearchParams } from "next/navigation";
7 +import Image from "next/image";
8 +import { invoke } from "@tauri-apps/api";
9 +import { TAURI_ACCOUNT_PROFILE } from "@/lib/tauri-handler";
10 +import { AccountProfileResponse } from "@/models/account";
11 +import { TauriResponse } from "@/models/shared";
12 +import { LocationMarkerIcon } from "@heroicons/react/solid";
13 +import { GearIcon } from "@radix-ui/react-icons";
14 +import Link from "next/link";
15 +import { PARAMS_USER_ID } from "@/app/_lib/params";
16 +import { UserContext } from "@/lib/current-user-context";
17 +
18 +enum State {
19 + LOADING,
20 + LOADED,
21 + ERROR,
22 +}
23 +
24 +export default function Index() {
25 + const log = useLogger("Profile/Page");
26 + const searchParams = useSearchParams();
27 + const userId = searchParams.get(PARAMS_USER_ID);
28 + const [isOwnProfile, setIsOwnProfile] = useState(false);
29 + const [userDisplayed, setUserDisplayed] = useState<User | null>(null);
30 + const [state, setState] = useState(State.LOADING);
31 + const userContext = useContext(UserContext);
32 + const loggedInUser = userContext.user?.user;
33 +
34 + useEffect(() => {
35 + log.debug("Profile page loaded.");
36 + async function fetchData() {
37 + try {
38 + const result = await invoke<AccountProfileResponse>(
39 + TAURI_ACCOUNT_PROFILE,
40 + { userId }
41 + );
42 + log.debug("ProfileResponse", result);
43 + if (result.status === TauriResponse.ERROR) {
44 + log.error("Error fetching profile");
45 + return;
46 + }
47 + if (!result.user) {
48 + log.error("No user found");
49 + setState(State.ERROR);
50 + return;
51 + }
52 +
53 + setIsOwnProfile(result.user.id === loggedInUser?.id);
54 + setUserDisplayed(result.user);
55 + setState(State.LOADED);
56 + } catch (error) {
57 + log.error(error);
58 + setState(State.ERROR);
59 + }
60 + }
61 + fetchData();
62 + // eslint-disable-next-line react-hooks/exhaustive-deps
63 + }, []);
64 +
65 + if (state === State.LOADING) {
66 + return (
67 + <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
68 + Loading...
69 + </div>
70 + );
71 + }
72 +
73 + if (state === State.ERROR) {
74 + return (
75 + <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
76 + Error loading profile
77 + </div>
78 + );
79 + }
80 +
81 + if (!userDisplayed) {
82 + return (
83 + <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
84 + User not found
85 + </div>
86 + );
87 + }
88 +
89 + return (
90 + <div className="w-full px-4 mx-auto">
91 + <div className="relative flex flex-col min-w-0 break-words mb-6 shadow-xl rounded-lg mt-16">
92 + <div className="px-6">
93 + <div className="flex flex-wrap justify-center">
94 + <div className="w-full px-4 flex justify-center">
95 + <div className="">
96 + <Image
97 + src={userDisplayed.gravatar_url}
98 + width={150}
99 + height={150}
100 + className="shadow-xl rounded-full h-auto align-middle border-none absolute -m-16 -ml-20 max-w-150-px"
101 + alt={userDisplayed.name}
102 + />
103 + </div>
104 + </div>
105 + <div className="w-full px-4 text-center mt-20">
106 + <div className="flex justify-center py-4 lg:pt-4 pt-8">
107 + <div className="mr-4 p-3 text-center">
108 + <span className="text-xl font-bold block uppercase tracking-wide text-blueGray-600">
109 + {userDisplayed.followers_count}
110 + </span>
111 + <span className="text-sm text-blueGray-400">Followers</span>
112 + </div>
113 + <div className="lg:mr-4 p-3 text-center">
114 + <span className="text-xl font-bold block uppercase tracking-wide text-blueGray-600">
115 + {userDisplayed.following_count}
116 + </span>
117 + <span className="text-sm text-blueGray-400">Following</span>
118 + </div>
119 + </div>
120 + </div>
121 + </div>
122 + <div className="text-center mt-12">
123 + <h3 className="text-xl font-semibold leading-normal text-blueGray-700 mb-2">
124 + {userDisplayed.name}{" "}
125 + <span className="text-blueGray-400 font-normal">
126 + (@{usernameOrId(userDisplayed)})
127 + </span>
128 + </h3>
129 + {isOwnProfile && (
130 + <div className="absolute -m-4 -mr-4">
131 + <Link href="/profile/update">
132 + <GearIcon className="h-6 w-6 text-blueGray-300" />
133 + </Link>
134 + </div>
135 + )}
136 + <div className="text-sm leading-normal mt-0 mb-2 text-blueGray-400 font-bold uppercase">
137 + <LocationMarkerIcon className="h-4 w-4 inline-block" /> Stockholm
138 + </div>
139 + </div>
140 + <div className="mt-10 py-10 border-t border-blueGray-200 text-center">
141 + <div className="flex flex-wrap justify-center">
142 + <div className="w-full lg:w-9/12 px-4">
143 + <p className="mb-4 text-lg leading-relaxed text-blueGray-700">
144 + {userDisplayed.about}
145 + </p>
146 + </div>
147 + </div>
148 + </div>
149 + </div>
150 + </div>
151 + </div>
152 + );
153 +}
frontend/splitfire-desktop/app/profile/page.tsx
+2 -143
@@ -1,152 +1,11 @@
1 "use client";
2
3 -import { useContext, useEffect, useState } from "react";
4 -import { useLogger } from "@/lib/logger";
5 -import User, { usernameOrId } from "@/models/user";
6 -import { useSearchParams } from "next/navigation";
7 -import Image from "next/image";
8 -import { invoke } from "@tauri-apps/api";
9 -import { TAURI_ACCOUNT_PROFILE } from "@/lib/tauriHandler";
10 -import { AccountProfileResponse } from "@/models/account";
11 -import { TauriResponse } from "@/models/shared";
12 -import { LocationMarkerIcon } from "@heroicons/react/solid";
13 -import { GearIcon } from "@radix-ui/react-icons";
14 -import Link from "next/link";
15 -import { PARAMS_USER_ID } from "@/app/_lib/params";
16 -import { UserContext } from "@/lib/CurrentUserContext";
17 -
18 -enum State {
19 - LOADING,
20 - LOADED,
21 - ERROR,
22 -}
3 +import Index from "./components";
4
5 export default function Page() {
25 - const log = useLogger("Profile/Page");
26 - const searchParams = useSearchParams();
27 - const userId = searchParams.get(PARAMS_USER_ID);
28 - const [isOwnProfile, setIsOwnProfile] = useState(false);
29 - const [userDisplayed, setUserDisplayed] = useState<User | null>(null);
30 - const [state, setState] = useState(State.LOADING);
31 - const userContext = useContext(UserContext);
32 - const loggedInUser = userContext.user?.user;
33 -
34 - useEffect(() => {
35 - log.debug("Profile page loaded.");
36 - async function fetchData() {
37 - try {
38 - const result = await invoke<AccountProfileResponse>(
39 - TAURI_ACCOUNT_PROFILE,
40 - { userId }
41 - );
42 - log.debug("ProfileResponse", result);
43 - if (result.status === TauriResponse.ERROR) {
44 - log.error("Error fetching profile");
45 - return;
46 - }
47 - if (!result.user) {
48 - log.error("No user found");
49 - setState(State.ERROR);
50 - return;
51 - }
52 -
53 - setIsOwnProfile(result.user.id === loggedInUser?.id);
54 - setUserDisplayed(result.user);
55 - setState(State.LOADED);
56 - } catch (error) {
57 - log.error(error);
58 - setState(State.ERROR);
59 - }
60 - }
61 - fetchData();
62 - // eslint-disable-next-line react-hooks/exhaustive-deps
63 - }, []);
64 -
65 - if (state === State.LOADING) {
66 - return (
67 - <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
68 - Loading...
69 - </div>
70 - );
71 - }
72 -
73 - if (state === State.ERROR) {
74 - return (
75 - <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
76 - Error loading profile
77 - </div>
78 - );
79 - }
80 -
81 - if (!userDisplayed) {
82 - return (
83 - <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
84 - User not found
85 - </div>
86 - );
87 - }
88 -
6 return (
7 <div className="prose prose-sm prose-invert max-w-none grid-rows-3 gap-6">
91 - <div className="w-full px-4 mx-auto">
92 - <div className="relative flex flex-col min-w-0 break-words mb-6 shadow-xl rounded-lg mt-16">
93 - <div className="px-6">
94 - <div className="flex flex-wrap justify-center">
95 - <div className="w-full px-4 flex justify-center">
96 - <div className="">
97 - <Image
98 - src={userDisplayed.gravatar_url}
99 - width={150}
100 - height={150}
101 - className="shadow-xl rounded-full h-auto align-middle border-none absolute -m-16 -ml-20 max-w-150-px"
102 - alt={userDisplayed.name}
103 - />
104 - </div>
105 - </div>
106 - <div className="w-full px-4 text-center mt-20">
107 - <div className="flex justify-center py-4 lg:pt-4 pt-8">
108 - <div className="mr-4 p-3 text-center">
109 - <span className="text-xl font-bold block uppercase tracking-wide text-blueGray-600">
110 - {userDisplayed.followers_count}
111 - </span>
112 - <span className="text-sm text-blueGray-400">Followers</span>
113 - </div>
114 - <div className="lg:mr-4 p-3 text-center">
115 - <span className="text-xl font-bold block uppercase tracking-wide text-blueGray-600">
116 - {userDisplayed.following_count}
117 - </span>
118 - <span className="text-sm text-blueGray-400">Following</span>
119 - </div>
120 - </div>
121 - </div>
122 - </div>
123 - <div className="text-center mt-12">
124 - <h3 className="text-xl font-semibold leading-normal text-blueGray-700 mb-2">
125 - {userDisplayed.name} <span className="text-blueGray-400 font-normal">(@{usernameOrId(userDisplayed)})</span>
126 - </h3>
127 - {isOwnProfile && (
128 - <div className="absolute -m-4 -mr-4">
129 - <Link href="/profile/update">
130 - <GearIcon className="h-6 w-6 text-blueGray-300" />
131 - </Link>
132 - </div>
133 - )}
134 - <div className="text-sm leading-normal mt-0 mb-2 text-blueGray-400 font-bold uppercase">
135 - <LocationMarkerIcon className="h-4 w-4 inline-block" /> Stockholm
136 - </div>
137 - </div>
138 - <div className="mt-10 py-10 border-t border-blueGray-200 text-center">
139 - <div className="flex flex-wrap justify-center">
140 - <div className="w-full lg:w-9/12 px-4">
141 - <p className="mb-4 text-lg leading-relaxed text-blueGray-700">
142 - {userDisplayed.about}
143 - </p>
144 - </div>
145 - </div>
146 - </div>
147 - </div>
148 - </div>
149 - </div>
8 + <Index />
9 </div>
10 );
11 }
frontend/splitfire-desktop/app/ready-to-play/page.tsx
+1 -1
@@ -3,7 +3,7 @@
3 import { ContentCarouselResponse } from "@/models/content";
4 import { invoke } from "@tauri-apps/api";
5 import { useEffect, useState } from "react";
6 -import { TAURI_CONTENT_READY_TO_PLAY } from "../../lib/tauriHandler";
6 +import { TAURI_CONTENT_READY_TO_PLAY } from "../../lib/tauri-handler";
7 import { SkeletonCard, SongProviderCard, SongProviderPath } from "../_ui/skeleton-card";
8 import { SongProvider } from "../../models/song-response";
9
frontend/splitfire-desktop/app/register/components/index.tsx new
+29
@@ -0,0 +1,29 @@
1 +"use client";
2 +
3 +import { useLogger } from "@/lib/logger";
4 +import { useState } from "react";
5 +import { State, SignupForm, SignupState } from "./signup-form";
6 +import SignupSuccess from "./signup-success";
7 +
8 +export function Index() {
9 + const log = useLogger("Register/Page");
10 + const [state, setState] = useState(State.LOADED);
11 +
12 + const registerCallback = (state: SignupState) => {
13 + log.debug("Register callback", state);
14 + if (state === SignupState.SUCCESS) {
15 + setState(State.SUCCESS);
16 + } else {
17 + setState(State.ERROR);
18 + }
19 + }
20 + const view = state === State.SUCCESS ? <SignupSuccess /> : <SignupForm registerCallback={registerCallback} />;
21 +
22 + return (
23 + <div className="prose prose-sm prose-invert max-w-none">
24 + <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
25 + {view}
26 + </div>
27 + </div>
28 + );
29 +}
\ No newline at end of file
frontend/splitfire-desktop/app/register/components/signup-form.tsx new
+158
@@ -0,0 +1,158 @@
1 +"use client";
2 +
3 +import { useLogger } from "@/lib/logger";
4 +import { TAURI_ACCOUNT_REGISTER } from "@/lib/tauri-handler";
5 +import { AccountRegisterResponse } from "@/models/account";
6 +import { TauriResponse } from "@/models/shared";
7 +import { invoke } from "@tauri-apps/api";
8 +import { useState } from "react";
9 +import * as Form from "@radix-ui/react-form";
10 +import { Button } from "@/app/_ui/components/button";
11 +
12 +type SignupFormPayload = {
13 + name: string,
14 + email: string,
15 + password: string
16 +}
17 +
18 +export enum State {
19 + LOADING,
20 + LOADED,
21 + ERROR,
22 + SUCCESS
23 +}
24 +
25 +export enum SignupState {
26 + ERROR,
27 + SUCCESS
28 +}
29 +
30 +export function SignupForm({ registerCallback }: { registerCallback: (state: SignupState) => void }) {
31 + const log = useLogger("Register/Form");
32 + const [name, setName] = useState("");
33 + const [email, setEmail] = useState("");
34 + const [password, setPassword] = useState("");
35 + const [state, setState] = useState(State.LOADED);
36 +
37 + const register = async () => {
38 + setState(State.LOADING);
39 + try {
40 + const payload: SignupFormPayload = {
41 + name,
42 + email,
43 + password
44 + };
45 + log.debug("payload", payload);
46 + const response = await invoke<AccountRegisterResponse>(TAURI_ACCOUNT_REGISTER, payload);
47 + log.debug("response", response);
48 + if (response.status === TauriResponse.ERROR) {
49 + log.error("Register failed", response);
50 + setState(State.ERROR);
51 + return;
52 + }
53 + setState(State.SUCCESS);
54 + // Call the callback to update the parent state
55 + registerCallback(SignupState.SUCCESS);
56 + } catch (error) {
57 + log.error("Register failed", error);
58 + setState(State.ERROR);
59 + }
60 + };
61 +
62 + const buttonText = state === State.LOADING ? "Registering..." : "Register";
63 +
64 + return (
65 + <Form.Root
66 + className="FormRoot"
67 + onSubmit={(event) => {
68 + event.preventDefault();
69 + }}
70 + >
71 + <Form.Field className="FormField my-6" name="name">
72 + <div
73 + style={{
74 + display: "flex",
75 + alignItems: "baseline",
76 + justifyContent: "space-between",
77 + }}
78 + >
79 + <Form.Label className="FormLabel">Name</Form.Label>
80 + <Form.Message className="FormMessage" match="valueMissing">
81 + Please enter your name
82 + </Form.Message>
83 + <Form.Message className="FormMessage" match="typeMismatch">
84 + Please provide a valid name
85 + </Form.Message>
86 + </div>
87 + <Form.Control asChild>
88 + <input
89 + className="Input"
90 + type="text"
91 + value={name}
92 + onChange={(e) => setName(e.currentTarget.value)}
93 + required
94 + />
95 + </Form.Control>
96 + </Form.Field>
97 + <Form.Field className="FormField" name="email">
98 + <div
99 + style={{
100 + display: "flex",
101 + alignItems: "baseline",
102 + justifyContent: "space-between",
103 + }}
104 + >
105 + <Form.Label className="FormLabel">Email</Form.Label>
106 + <Form.Message className="FormMessage" match="valueMissing">
107 + Please enter your email
108 + </Form.Message>
109 + <Form.Message className="FormMessage" match="typeMismatch">
110 + Please provide a valid email
111 + </Form.Message>
112 + </div>
113 + <Form.Control asChild>
114 + <input
115 + className="Input"
116 + type="email"
117 + value={email}
118 + onChange={(e) => setEmail(e.currentTarget.value)}
119 + required
120 + />
121 + </Form.Control>
122 + </Form.Field>
123 + <Form.Field className="FormField my-6" name="password">
124 + <div
125 + style={{
126 + display: "flex",
127 + alignItems: "baseline",
128 + justifyContent: "space-between",
129 + }}
130 + >
131 + <Form.Label className="FormLabel">Password</Form.Label>
132 + <Form.Message className="FormMessage" match="valueMissing">
133 + Please enter your email
134 + </Form.Message>
135 + <Form.Message className="FormMessage" match="typeMismatch">
136 + Please provide a valid email
137 + </Form.Message>
138 + </div>
139 + <Form.Control asChild>
140 + <input
141 + className="Input"
142 + type="password"
143 + value={password}
144 + onChange={(e) => {
145 + setPassword(e.currentTarget.value);
146 + }}
147 + required
148 + />
149 + </Form.Control>
150 + </Form.Field>
151 + <Form.Field className="FormField my-6" name="button">
152 + <Button onClick={register} variant={"outline"} size={"lg"}>
153 + {buttonText}
154 + </Button>
155 + </Form.Field>
156 + </Form.Root>
157 + )
158 +}
\ No newline at end of file
frontend/splitfire-desktop/app/register/components/signup-success.tsx new
+11
@@ -0,0 +1,11 @@
1 +"use client";
2 +
3 +export default function SignupSuccess() {
4 + return (
5 + <div>
6 + <h1>Success</h1>
7 + <p>Your account has been created successfully.</p>
8 + <p>Check your email to confirm your account.</p>
9 + </div>
10 + );
11 +}
\ No newline at end of file
frontend/splitfire-desktop/app/register/page.tsx
+2 -179
@@ -1,190 +1,13 @@
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 "../../lib/tauriHandler";
7 -import { Button } from "../_ui/components/button";
8 -import { useLogger } from "../../lib/logger";
9 -import { AccountRegisterResponse } from "@/models/account";
10 -import { TauriResponse } from "@/models/shared";
11 -
12 -enum State {
13 - LOADING,
14 - LOADED,
15 - ERROR,
16 - SUCCESS
17 -}
18 -
19 -enum SignupState {
20 - ERROR,
21 - SUCCESS
22 -}
23 -
24 -type SignupFormPayload = {
25 - name: string,
26 - email: string,
27 - password: string
28 -}
3 +import { Index } from "./components";
4
5 export default function Page() {
31 - const log = useLogger("Register/Page");
32 - const [state, setState] = useState(State.LOADED);
33 -
34 - const registerCallback = (state: SignupState) => {
35 - log.debug("Register callback", state);
36 - if (state === SignupState.SUCCESS) {
37 - setState(State.SUCCESS);
38 - } else {
39 - setState(State.ERROR);
40 - }
41 - }
42 - const view = state === State.SUCCESS ? <SignupSuccess /> : <SignupForm registerCallback={registerCallback} />;
6 return (
7 <div className="prose prose-sm prose-invert max-w-none">
8 <div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
46 - {view}
9 + <Index />
10 </div>
11 </div>
12 );
50 -}
51 -
52 -function SignupSuccess() {
53 - return (
54 - <div>
55 - <h1>Success</h1>
56 - <p>Your account has been created successfully.</p>
57 - <p>Check your email to confirm your account.</p>
58 - </div>
59 - );
60 -}
61 -
62 -function SignupForm({ registerCallback }: { registerCallback: (state: SignupState) => void }) {
63 - const log = useLogger("Register/Form");
64 - const [name, setName] = useState("");
65 - const [email, setEmail] = useState("");
66 - const [password, setPassword] = useState("");
67 - const [state, setState] = useState(State.LOADED);
68 -
69 - const register = async () => {
70 - setState(State.LOADING);
71 - try {
72 - const payload: SignupFormPayload = {
73 - name,
74 - email,
75 - password
76 - };
77 - log.debug("payload", payload);
78 - const response = await invoke<AccountRegisterResponse>(TAURI_ACCOUNT_REGISTER, payload);
79 - log.debug("response", response);
80 - if (response.status === TauriResponse.ERROR) {
81 - log.error("Register failed", response);
82 - setState(State.ERROR);
83 - return;
84 - }
85 - setState(State.SUCCESS);
86 - // Call the callback to update the parent state
87 - registerCallback(SignupState.SUCCESS);
88 - } catch (error) {
89 - log.error("Register failed", error);
90 - setState(State.ERROR);
91 - }
92 - };
93 -
94 - const buttonText = state === State.LOADING ? "Registering..." : "Register";
95 -
96 - return (
97 - <Form.Root
98 - className="FormRoot"
99 - onSubmit={(event) => {
100 - event.preventDefault();
101 - }}
102 - >
103 - <Form.Field className="FormField my-6" name="name">
104 - <div
105 - style={{
106 - display: "flex",
107 - alignItems: "baseline",
108 - justifyContent: "space-between",
109 - }}
110 - >
111 - <Form.Label className="FormLabel">Name</Form.Label>
112 - <Form.Message className="FormMessage" match="valueMissing">
113 - Please enter your name
114 - </Form.Message>
115 - <Form.Message className="FormMessage" match="typeMismatch">
116 - Please provide a valid name
117 - </Form.Message>
118 - </div>
119 - <Form.Control asChild>
120 - <input
121 - className="Input"
122 - type="text"
123 - value={name}
124 - onChange={(e) => setName(e.currentTarget.value)}
125 - required
126 - />
127 - </Form.Control>
128 - </Form.Field>
129 - <Form.Field className="FormField" name="email">
130 - <div
131 - style={{
132 - display: "flex",
133 - alignItems: "baseline",
134 - justifyContent: "space-between",
135 - }}
136 - >
137 - <Form.Label className="FormLabel">Email</Form.Label>
138 - <Form.Message className="FormMessage" match="valueMissing">
139 - Please enter your email
140 - </Form.Message>
141 - <Form.Message className="FormMessage" match="typeMismatch">
142 - Please provide a valid email
143 - </Form.Message>
144 - </div>
145 - <Form.Control asChild>
146 - <input
147 - className="Input"
148 - type="email"
149 - value={email}
150 - onChange={(e) => setEmail(e.currentTarget.value)}
151 - required
152 - />
153 - </Form.Control>
154 - </Form.Field>
155 - <Form.Field className="FormField my-6" name="password">
156 - <div
157 - style={{
158 - display: "flex",
159 - alignItems: "baseline",
160 - justifyContent: "space-between",
161 - }}
162 - >
163 - <Form.Label className="FormLabel">Password</Form.Label>
164 - <Form.Message className="FormMessage" match="valueMissing">
165 - Please enter your email
166 - </Form.Message>
167 - <Form.Message className="FormMessage" match="typeMismatch">
168 - Please provide a valid email
169 - </Form.Message>
170 - </div>
171 - <Form.Control asChild>
172 - <input
173 - className="Input"
174 - type="password"
175 - value={password}
176 - onChange={(e) => {
177 - setPassword(e.currentTarget.value);
178 - }}
179 - required
180 - />
181 - </Form.Control>
182 - </Form.Field>
183 - <Form.Field className="FormField my-6" name="button">
184 - <Button onClick={register} variant={"outline"} size={"lg"}>
185 - {buttonText}
186 - </Button>
187 - </Form.Field>
188 - </Form.Root>
189 - )
13 }
\ No newline at end of file
frontend/splitfire-desktop/app/split/_components/button-split.tsx
+1 -1
@@ -1,4 +1,4 @@
1 -import { UserContext } from "@/lib/CurrentUserContext";
1 +import { UserContext } from "@/lib/current-user-context";
2 import { AudioFile, Status } from "@/models/audio-file";
3 import { CountdownTimerIcon, LapTimerIcon, RocketIcon } from "@radix-ui/react-icons";
4 import { useRouter } from "next/navigation";
frontend/splitfire-desktop/app/split/_components/song-votes.tsx
+1 -1
@@ -1,9 +1,9 @@
1 "use client";
2
3 import { SongProvider } from "@/models/song-response";
4 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
4 import VotesView from "./votes-view";
5 import SongVotesImage from "./song-votes-image";
6 +import { SongProviderVote } from "@/models/song-votes-detail-response";
7
8 export default function SongVotes({
9 songProvider,
frontend/splitfire-desktop/app/split/_components/voters-view.tsx
+2 -1
@@ -1,9 +1,10 @@
1 +"use client";
2
2 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
3 import { PARAMS_USER_ID } from '@/app/_lib/params';
4 import Image from "next/image";
5 import Link from "next/link";
6 import { VoteType } from "./votes-view-main";
7 +import { SongProviderVote } from '@/models/song-votes-detail-response';
8
9 export function VoterGravatarsViews(props: {
10 voters: SongProviderVote[];
frontend/splitfire-desktop/app/split/_components/votes-view-main.tsx
+3 -3
@@ -2,15 +2,15 @@ import { BsArrowUpCircleFill, BsArrowDownCircleFill } from "react-icons/bs";
2 import { VoterGravatarsViews } from "./voters-view";
3 import { SongProviderResponse } from "@/models/content";
4 import { CSSProperties, useContext, useState } from "react";
5 -import { TAURI_CONTENT_SONG_BRIDGE_VOTE } from "@/lib/tauriHandler";
6 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
5 +import { TAURI_CONTENT_SONG_BRIDGE_VOTE } from "@/lib/tauri-handler";
6 import { TauriResponse } from "@/models/shared";
7 import { invoke } from "@tauri-apps/api";
9 -import { UserContext } from "@//lib/CurrentUserContext";
8 +import { UserContext } from "@//lib/current-user-context";
9 import { useRouter } from "next/navigation";
10 import { CurrentUser } from "@/lib/db";
11 import { Spinner } from "react-bootstrap";
12 import { useLogger } from "@/lib/logger";
13 +import { SongProviderVote } from "@/models/song-votes-detail-response";
14
15 export enum VoteType {
16 UP = "up",
frontend/splitfire-desktop/app/split/_components/votes-view.tsx
+1 -1
@@ -4,13 +4,13 @@ import {
4 AudioFile,
5 Status,
6 } from "@/models/audio-file";
7 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
7 import { useRouter } from "next/navigation";
8 import { useState } from "react";
9 import ButtonGenerateBackingTracks from "./button-split";
10 import MainVotesView, { } from "./votes-view-main";
11 import LetsPlayView from "./lets-play";
12 import { useLogger } from "@/lib/logger";
13 +import { SongProviderVote } from "@/models/song-votes-detail-response";
14
15 export default function VotesView({
16 songProviderId,
frontend/splitfire-desktop/app/split/page.tsx
+2 -2
@@ -4,11 +4,11 @@ import { useSearchParams } from "next/navigation";
4 import { useLogger } from "@/lib/logger";
5 import { useEffect, useState } from "react";
6 import { invoke } from "@tauri-apps/api";
7 -import { TAURI_CONTENT_SONG_BRIDGE_DETAIL } from "@/lib/tauriHandler";
7 +import { TAURI_CONTENT_SONG_BRIDGE_DETAIL } from "@/lib/tauri-handler";
8 import { SongProvider } from "@/models/song-response";
9 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
9 import SongVotes from "./_components/song-votes";
10 import { SongBridgeResponse } from "@/models/content";
11 +import { SongProviderVote } from "@/models/song-votes-detail-response";
12
13 export default function Page() {
14 const log = useLogger("Play");
frontend/splitfire-desktop/app/top-voted/components/index.tsx
+1 -1
@@ -2,7 +2,7 @@
2
3 import { SkeletonCard, SongProviderCard, SongProviderPath } from "@/app/_ui/skeleton-card";
4 import { useLogger } from "@/lib/logger";
5 -import { TAURI_CONTENT_TOP_VOTED } from "@/lib/tauriHandler";
5 +import { TAURI_CONTENT_TOP_VOTED } from "@/lib/tauri-handler";
6 import { ContentCarouselResponse } from "@/models/content";
7 import { SongProvider } from "@/models/song-response";
8 import { invoke } from "@tauri-apps/api";
frontend/splitfire-desktop/lib/current-user-context.ts renamed
frontend/splitfire-desktop/lib/tauri-handler.ts renamed
frontend/splitfire-desktop/lib/use-count-down.ts renamed
frontend/splitfire-desktop/models/content.ts
+1 -1
@@ -1,6 +1,6 @@
1 import { SongProvider } from "@/models/song-response";
2 import { TauriResponse } from "./shared";
3 -import { SongProviderVote } from "@/models/SongVotesDetailResponse";
3 +import { SongProviderVote } from "./song-votes-detail-response";
4
5 // Need to be renamed into more generic name
6 export interface ContentCarouselResponse {
frontend/splitfire-desktop/models/song-response.ts renamed
frontend/splitfire-desktop/models/song-votes-detail-response.ts renamed
frontend/splitfire-desktop/models/user.ts renamed