reamed user metrics into user data and added avatar picture and started to implement the user settings page

This commit is contained in:
Clément Le Bihan
2023-04-09 18:56:45 +02:00
parent fbf85a635e
commit 572bb0056d
6 changed files with 130 additions and 58 deletions

View File

@@ -33,9 +33,9 @@ export class APIError extends Error {
constructor(
message: string,
public status: number,
// Set the message to the correct error this is a placeholder
// Set the message to the correct error this is a placeholder
// when the error is only used internally (middleman)
public userMessage : keyof typeof en = "unknownError"
public userMessage: keyof typeof en = "unknownError"
) {
super(message);
}
@@ -54,7 +54,8 @@ const dummyIllustrations = [
"https://upload.wikimedia.org/wikipedia/en/b/ba/David_Guetta_2U.jpg",
];
const getDummyIllustration = () => dummyIllustrations[Math.floor(Math.random() * dummyIllustrations.length)];
const getDummyIllustration = () =>
dummyIllustrations[Math.floor(Math.random() * dummyIllustrations.length)];
// we will need the same thing for the scorometer API url
const baseAPIUrl =
@@ -63,7 +64,6 @@ const baseAPIUrl =
: Constants.manifest?.extra?.apiUrl;
export default class API {
public static async fetch(params: FetchParams) {
const jwtToken = store.getState().user.accessToken;
const header = {
@@ -110,7 +110,8 @@ export default class API {
.catch((e) => {
if (!(e instanceof APIError)) throw e;
if (e.status == 401) throw new APIError("invalidCredentials", 401, "invalidCredentials");
if (e.status == 401)
throw new APIError("invalidCredentials", 401, "invalidCredentials");
throw e;
});
}
@@ -127,7 +128,7 @@ export default class API {
body: registrationInput,
method: "POST",
});
// In the Future we should move autheticate out of this function
// In the Future we should move autheticate out of this function
// and maybe create a new function to create and login in one go
return API.authenticate({
username: registrationInput.username,
@@ -140,7 +141,8 @@ export default class API {
route: "/auth/guest",
method: "POST",
});
if (!response.access_token) throw new APIError("No access token", response.status);
if (!response.access_token)
throw new APIError("No access token", response.status);
return response.access_token;
}
@@ -148,14 +150,8 @@ export default class API {
* Retrieve information of the currently authentified user
*/
public static async getUserInfo(): Promise<User> {
let me = await API.fetch({
route: "/auth/me",
});
// /auth/me only returns username and id (it needs to be changed)
let user = await API.fetch({
route: `/users/${me.id}`,
route: "/auth/me",
});
// this a dummy settings object, we will need to fetch the real one from the API
@@ -163,10 +159,12 @@ export default class API {
id: user.id as number,
name: (user.username ?? user.name) as string,
email: user.email as string,
xp: 0,
premium: false,
metrics: {
data: {
partyPlayed: user.partyPlayed as number,
xp: 0,
avatar:
"https://imgs.search.brave.com/RnQpFhmAFvuQsN_xTw7V-CN61VeHDBg2tkEXnKRYHAE/rs:fit:768:512:1/g:ce/aHR0cHM6Ly96b29h/c3Ryby5jb20vd3At/Y29udGVudC91cGxv/YWRzLzIwMjEvMDIv/Q2FzdG9yLTc2OHg1/MTIuanBn",
},
settings: {
preferences: {
@@ -217,16 +215,19 @@ export default class API {
});
// this is a dummy illustration, we will need to fetch the real one from the API
return songs.data.map((song: any) => ({
id: song.id as number,
name: song.name as string,
artistId: song.artistId as number,
albumId: song.albumId as number,
genreId: song.genreId as number,
details: song.difficulties,
cover: getDummyIllustration(),
metrics: {},
} as Song));
return songs.data.map(
(song: any) =>
({
id: song.id as number,
name: song.name as string,
artistId: song.artistId as number,
albumId: song.albumId as number,
genreId: song.genreId as number,
details: song.difficulties,
cover: getDummyIllustration(),
metrics: {},
} as Song)
);
}
/**
@@ -247,7 +248,6 @@ export default class API {
genreId: song.genreId as number,
details: song.difficulties,
cover: getDummyIllustration(),
metrics: {},
} as Song;
}
/**
@@ -316,7 +316,7 @@ export default class API {
*/
public static async searchSongs(query: string): Promise<Song[]> {
return API.fetch({
route: `/search/guess/song/${query}`
route: `/search/guess/song/${query}`,
});
}
@@ -340,7 +340,10 @@ export default class API {
*/
public static async getSearchHistory(): Promise<Song[]> {
const queryClient = new QueryClient();
let songs = await queryClient.fetchQuery(["API", "allsongs"], API.getAllSongs);
let songs = await queryClient.fetchQuery(
["API", "allsongs"],
API.getAllSongs
);
const shuffled = [...songs].sort(() => 0.5 - Math.random());
return shuffled.slice(0, 2);
@@ -359,7 +362,10 @@ export default class API {
*/
public static async getUserPlayHistory(): Promise<Song[]> {
const queryClient = new QueryClient();
let songs = await queryClient.fetchQuery(["API", "allsongs"], API.getAllSongs);
let songs = await queryClient.fetchQuery(
["API", "allsongs"],
API.getAllSongs
);
const shuffled = [...songs].sort(() => 0.5 - Math.random());
return shuffled.slice(0, 3);
@@ -447,7 +453,10 @@ export default class API {
return rep;
}
public static async updateUserPassword(oldPassword: string, newPassword: string): Promise<User> {
public static async updateUserPassword(
oldPassword: string,
newPassword: string
): Promise<User> {
const rep = await API.fetch({
route: "/auth/me",
method: "PUT",
@@ -461,5 +470,5 @@ export default class API {
throw new Error(rep.error);
}
return rep;
};
}
}

View File

@@ -1,5 +0,0 @@
interface Metrics {
partyPlayed: number;
}
export default Metrics;

View File

@@ -1,4 +1,3 @@
import Metrics from "./Metrics";
import Model from "./Model";
import SongDetails from "./SongDetails";
@@ -8,7 +7,6 @@ interface Song extends Model {
albumId: number | null
genreId: number | null;
cover: string;
metrics: Metrics;
details: SongDetails;
}

View File

@@ -1,4 +1,4 @@
import Metrics from "./Metrics";
import UserData from "./UserData";
import Model from "./Model";
import UserSettings from "./UserSettings";
@@ -6,9 +6,8 @@ interface User extends Model {
name: string;
email: string;
isGuest: boolean;
xp: number;
premium: boolean;
metrics: Metrics;
data: UserData;
settings: UserSettings;
}

7
front/models/UserData.ts Normal file
View File

@@ -0,0 +1,7 @@
interface UserData {
partyPlayed: number;
xp: number;
avatar: string | undefined;
}
export default UserData;

View File

@@ -3,9 +3,33 @@ import { useDispatch } from "react-redux";
import { unsetAccessToken } from "../../state/UserSlice";
import React, { useEffect, useState } from "react";
import { Column, Text, Button, Icon, Box, Center, Heading } from "native-base";
import {
Column,
Text,
Button,
Icon,
Box,
IconButton,
Flex,
Row,
Center,
Heading,
Avatar,
} from "native-base";
import { FontAwesome5 } from "@expo/vector-icons";
import User from "../../models/User";
import TextButton from "../../components/TextButton";
import LoadingComponent from "../../components/Loading";
const getInitials = (name: string) => {
const names = name.split(" ");
if (names.length === 1) return names[0]?.charAt(0);
let initials = [];
for (let i = 0; i < names.length; i++) {
initials.push(names[i]?.charAt(0));
}
return initials.join("");
};
const ProfileSettings = ({ navigation }: { navigation: any }) => {
const [user, setUser] = useState<User | null>(null);
@@ -17,25 +41,65 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
});
}, []);
if (!user) {
return (
<Center style={{ flex: 1 }}>
<LoadingComponent />
</Center>
);
}
return (
<Center style={{ flex: 1}}>
{user && (
<Column>
<Heading>Profile Settings</Heading>
<Flex
style={{
flex: 1,
alignItems: "center",
paddingTop: 40,
}}
>
<Column>
<Center>
<Avatar size="2xl" source={{ uri: user.data.avatar }}>
{getInitials(user.name)}
<Avatar.Badge bg="gray.300" size={35}>
<IconButton size={"sm"} icon={<Icon as={FontAwesome5} name="edit" />} />
</Avatar.Badge>
</Avatar>
</Center>
<Row
style={{
paddingTop: 20,
alignItems: "center",
}}
>
<Heading>{user.name}</Heading>
<Button
ml={2}
size="sm"
leftIcon={<Icon as={FontAwesome5} name="edit" size="sm" />}
variant="ghost"
colorScheme="primary"
style={{
borderRadius: 10,
}}
></Button>
</Row>
<Text>Username: {user.name}</Text>
<Text>ID: {user.id}</Text>
<Text>Email: {user.email}</Text>
<Text>Party played: {user.metrics.partyPlayed}</Text>
<Text>Username: {user.name}</Text>
<Text>ID: {user.id}</Text>
<Text>Email: {user.email}</Text>
<Text>Party played: {user.data.partyPlayed}</Text>
<Text>XP: {user.xp}</Text>
</Column>
)}
<Text>XP: {user.data.xp}</Text>
</Column>
<TextButton onPress={() => dispatch(unsetAccessToken())} translate={{
translationKey: "signOutBtn",
}} />
</Center>
<TextButton
onPress={() => dispatch(unsetAccessToken())}
translate={{
translationKey: "signOutBtn",
}}
/>
</Flex>
);
};