reamed user metrics into user data and added avatar picture and started to implement the user settings page
This commit is contained in:
+41
-32
@@ -33,9 +33,9 @@ export class APIError extends Error {
|
|||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public status: number,
|
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)
|
// when the error is only used internally (middleman)
|
||||||
public userMessage : keyof typeof en = "unknownError"
|
public userMessage: keyof typeof en = "unknownError"
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,8 @@ const dummyIllustrations = [
|
|||||||
"https://upload.wikimedia.org/wikipedia/en/b/ba/David_Guetta_2U.jpg",
|
"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
|
// we will need the same thing for the scorometer API url
|
||||||
const baseAPIUrl =
|
const baseAPIUrl =
|
||||||
@@ -63,7 +64,6 @@ const baseAPIUrl =
|
|||||||
: Constants.manifest?.extra?.apiUrl;
|
: Constants.manifest?.extra?.apiUrl;
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
|
|
||||||
public static async fetch(params: FetchParams) {
|
public static async fetch(params: FetchParams) {
|
||||||
const jwtToken = store.getState().user.accessToken;
|
const jwtToken = store.getState().user.accessToken;
|
||||||
const header = {
|
const header = {
|
||||||
@@ -110,7 +110,8 @@ export default class API {
|
|||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
if (!(e instanceof APIError)) throw 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;
|
throw e;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ export default class API {
|
|||||||
body: registrationInput,
|
body: registrationInput,
|
||||||
method: "POST",
|
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
|
// and maybe create a new function to create and login in one go
|
||||||
return API.authenticate({
|
return API.authenticate({
|
||||||
username: registrationInput.username,
|
username: registrationInput.username,
|
||||||
@@ -140,7 +141,8 @@ export default class API {
|
|||||||
route: "/auth/guest",
|
route: "/auth/guest",
|
||||||
method: "POST",
|
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;
|
return response.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,14 +150,8 @@ export default class API {
|
|||||||
* Retrieve information of the currently authentified user
|
* Retrieve information of the currently authentified user
|
||||||
*/
|
*/
|
||||||
public static async getUserInfo(): Promise<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({
|
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
|
// 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,
|
id: user.id as number,
|
||||||
name: (user.username ?? user.name) as string,
|
name: (user.username ?? user.name) as string,
|
||||||
email: user.email as string,
|
email: user.email as string,
|
||||||
xp: 0,
|
|
||||||
premium: false,
|
premium: false,
|
||||||
metrics: {
|
data: {
|
||||||
partyPlayed: user.partyPlayed as number,
|
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: {
|
settings: {
|
||||||
preferences: {
|
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
|
// this is a dummy illustration, we will need to fetch the real one from the API
|
||||||
return songs.data.map((song: any) => ({
|
return songs.data.map(
|
||||||
id: song.id as number,
|
(song: any) =>
|
||||||
name: song.name as string,
|
({
|
||||||
artistId: song.artistId as number,
|
id: song.id as number,
|
||||||
albumId: song.albumId as number,
|
name: song.name as string,
|
||||||
genreId: song.genreId as number,
|
artistId: song.artistId as number,
|
||||||
details: song.difficulties,
|
albumId: song.albumId as number,
|
||||||
cover: getDummyIllustration(),
|
genreId: song.genreId as number,
|
||||||
metrics: {},
|
details: song.difficulties,
|
||||||
} as Song));
|
cover: getDummyIllustration(),
|
||||||
|
metrics: {},
|
||||||
|
} as Song)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,7 +248,6 @@ export default class API {
|
|||||||
genreId: song.genreId as number,
|
genreId: song.genreId as number,
|
||||||
details: song.difficulties,
|
details: song.difficulties,
|
||||||
cover: getDummyIllustration(),
|
cover: getDummyIllustration(),
|
||||||
metrics: {},
|
|
||||||
} as Song;
|
} as Song;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -316,7 +316,7 @@ export default class API {
|
|||||||
*/
|
*/
|
||||||
public static async searchSongs(query: string): Promise<Song[]> {
|
public static async searchSongs(query: string): Promise<Song[]> {
|
||||||
return API.fetch({
|
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[]> {
|
public static async getSearchHistory(): Promise<Song[]> {
|
||||||
const queryClient = new QueryClient();
|
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());
|
const shuffled = [...songs].sort(() => 0.5 - Math.random());
|
||||||
|
|
||||||
return shuffled.slice(0, 2);
|
return shuffled.slice(0, 2);
|
||||||
@@ -359,7 +362,10 @@ export default class API {
|
|||||||
*/
|
*/
|
||||||
public static async getUserPlayHistory(): Promise<Song[]> {
|
public static async getUserPlayHistory(): Promise<Song[]> {
|
||||||
const queryClient = new QueryClient();
|
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());
|
const shuffled = [...songs].sort(() => 0.5 - Math.random());
|
||||||
|
|
||||||
return shuffled.slice(0, 3);
|
return shuffled.slice(0, 3);
|
||||||
@@ -447,7 +453,10 @@ export default class API {
|
|||||||
return rep;
|
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({
|
const rep = await API.fetch({
|
||||||
route: "/auth/me",
|
route: "/auth/me",
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
@@ -461,5 +470,5 @@ export default class API {
|
|||||||
throw new Error(rep.error);
|
throw new Error(rep.error);
|
||||||
}
|
}
|
||||||
return rep;
|
return rep;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
interface Metrics {
|
|
||||||
partyPlayed: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Metrics;
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import Metrics from "./Metrics";
|
|
||||||
import Model from "./Model";
|
import Model from "./Model";
|
||||||
import SongDetails from "./SongDetails";
|
import SongDetails from "./SongDetails";
|
||||||
|
|
||||||
@@ -8,7 +7,6 @@ interface Song extends Model {
|
|||||||
albumId: number | null
|
albumId: number | null
|
||||||
genreId: number | null;
|
genreId: number | null;
|
||||||
cover: string;
|
cover: string;
|
||||||
metrics: Metrics;
|
|
||||||
details: SongDetails;
|
details: SongDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Metrics from "./Metrics";
|
import UserData from "./UserData";
|
||||||
import Model from "./Model";
|
import Model from "./Model";
|
||||||
import UserSettings from "./UserSettings";
|
import UserSettings from "./UserSettings";
|
||||||
|
|
||||||
@@ -6,9 +6,8 @@ interface User extends Model {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
xp: number;
|
|
||||||
premium: boolean;
|
premium: boolean;
|
||||||
metrics: Metrics;
|
data: UserData;
|
||||||
settings: UserSettings;
|
settings: UserSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
interface UserData {
|
||||||
|
partyPlayed: number;
|
||||||
|
xp: number;
|
||||||
|
avatar: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserData;
|
||||||
@@ -3,9 +3,33 @@ import { useDispatch } from "react-redux";
|
|||||||
import { unsetAccessToken } from "../../state/UserSlice";
|
import { unsetAccessToken } from "../../state/UserSlice";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
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 User from "../../models/User";
|
||||||
import TextButton from "../../components/TextButton";
|
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 ProfileSettings = ({ navigation }: { navigation: any }) => {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
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 (
|
return (
|
||||||
<Center style={{ flex: 1}}>
|
<Flex
|
||||||
{user && (
|
style={{
|
||||||
<Column>
|
flex: 1,
|
||||||
<Heading>Profile Settings</Heading>
|
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>Username: {user.name}</Text>
|
||||||
<Text>ID: {user.id}</Text>
|
<Text>ID: {user.id}</Text>
|
||||||
<Text>Email: {user.email}</Text>
|
<Text>Email: {user.email}</Text>
|
||||||
<Text>Party played: {user.metrics.partyPlayed}</Text>
|
<Text>Party played: {user.data.partyPlayed}</Text>
|
||||||
|
|
||||||
<Text>XP: {user.xp}</Text>
|
<Text>XP: {user.data.xp}</Text>
|
||||||
</Column>
|
</Column>
|
||||||
)}
|
|
||||||
|
|
||||||
<TextButton onPress={() => dispatch(unsetAccessToken())} translate={{
|
<TextButton
|
||||||
translationKey: "signOutBtn",
|
onPress={() => dispatch(unsetAccessToken())}
|
||||||
}} />
|
translate={{
|
||||||
</Center>
|
translationKey: "signOutBtn",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user