mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-26 08:47:36 +08:00
Get profile by name (#3585)
* handling query param getting user by name if query param is present * added function to get user by name * split profile function to get user by uid or name * added function to check if get parameter exists in url * updating the profile based on the url parameter * using query param in url * using get param * renamed param name adding search param to pathname in the deafult parameter value * renamed param * renamed param added query validation * extracted repeated query to a function * missing await * fixed typo fixed validation * using em for dynamic font sizes * using em for dynamic font sizes * added line height * using em * using em * using page profile search instead * updated the way data is passed into the page * profile search page * setting line height * removed vertical align * moved navigate to an event to avoid circular deps * fixed route controller not being included * removed unnecessary test * showing 404 error * improved query checking * renamed query param * fixed test * note * yeet * cleaner type definition
This commit is contained in:
parent
3cc55d634f
commit
409f0a83e4
25 changed files with 376 additions and 64 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import request from "supertest";
|
||||
import app from "../../../src/app";
|
||||
import * as Configuration from "../../../src/init/configuration";
|
||||
|
||||
const mockApp = request(app);
|
||||
|
||||
|
|
@ -17,8 +18,42 @@ describe("user controller test", () => {
|
|||
name: "NewUser",
|
||||
uid: "123456789",
|
||||
email: "newuser@mail.com",
|
||||
captcha: "captcha",
|
||||
};
|
||||
|
||||
jest.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue({
|
||||
//if stuff breaks this might be the reason
|
||||
users: {
|
||||
signUp: true,
|
||||
discordIntegration: {
|
||||
enabled: false,
|
||||
},
|
||||
autoBan: {
|
||||
enabled: false,
|
||||
maxCount: 5,
|
||||
maxHours: 1,
|
||||
},
|
||||
profiles: {
|
||||
enabled: false,
|
||||
},
|
||||
xp: {
|
||||
enabled: false,
|
||||
gainMultiplier: 0,
|
||||
maxDailyBonus: 0,
|
||||
minDailyBonus: 0,
|
||||
streak: {
|
||||
enabled: false,
|
||||
maxStreakDays: 0,
|
||||
maxStreakMultiplier: 0,
|
||||
},
|
||||
},
|
||||
inbox: {
|
||||
enabled: false,
|
||||
maxMail: 0,
|
||||
},
|
||||
},
|
||||
} as any);
|
||||
|
||||
await mockApp
|
||||
.post("/users/signup")
|
||||
.send(newUser)
|
||||
|
|
@ -51,6 +86,8 @@ describe("user controller test", () => {
|
|||
Accept: "application/json",
|
||||
})
|
||||
.expect(409);
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -438,7 +438,14 @@ export async function removeFavoriteQuote(
|
|||
export async function getProfile(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.params;
|
||||
const { uidOrName } = req.params;
|
||||
|
||||
const { isUid } = req.query;
|
||||
|
||||
const user =
|
||||
isUid !== undefined
|
||||
? await UserDAL.getUser(uidOrName, "get user profile")
|
||||
: await UserDAL.getUserByName(uidOrName, "get user profile");
|
||||
|
||||
const {
|
||||
name,
|
||||
|
|
@ -454,7 +461,7 @@ export async function getProfile(
|
|||
discordAvatar,
|
||||
xp,
|
||||
streak,
|
||||
} = await UserDAL.getUser(uid, "get user profile");
|
||||
} = user;
|
||||
|
||||
const validTimePbs = _.pick(personalBests?.time, "15", "30", "60", "120");
|
||||
const validWordsPbs = _.pick(personalBests?.words, "10", "25", "50", "100");
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ const requireProfilesEnabled = validateConfiguration({
|
|||
});
|
||||
|
||||
router.get(
|
||||
"/:uid/profile",
|
||||
"/:uidOrName/profile",
|
||||
requireProfilesEnabled,
|
||||
authenticateRequest({
|
||||
isPublic: true,
|
||||
|
|
@ -430,7 +430,10 @@ router.get(
|
|||
withApeRateLimiter(RateLimit.userProfileGet),
|
||||
validateRequest({
|
||||
params: {
|
||||
uid: joi.string().required(),
|
||||
uidOrName: joi.string().required(),
|
||||
},
|
||||
query: {
|
||||
isUid: joi.string().allow(""),
|
||||
},
|
||||
}),
|
||||
asyncHandler(UserController.getProfile)
|
||||
|
|
|
|||
|
|
@ -136,16 +136,6 @@ export async function clearPb(uid: string): Promise<void> {
|
|||
);
|
||||
}
|
||||
|
||||
export async function isNameAvailable(name: string): Promise<boolean> {
|
||||
const nameDocs = await getUsersCollection()
|
||||
.find({ name })
|
||||
.collation({ locale: "en", strength: 1 })
|
||||
.limit(1)
|
||||
.toArray();
|
||||
|
||||
return nameDocs.length === 0;
|
||||
}
|
||||
|
||||
export async function updateQuoteRatings(
|
||||
uid: string,
|
||||
quoteRatings: MonkeyTypes.UserQuoteRatings
|
||||
|
|
@ -175,6 +165,29 @@ export async function getUser(
|
|||
return user;
|
||||
}
|
||||
|
||||
async function findByName(name: string): Promise<MonkeyTypes.User | undefined> {
|
||||
return (
|
||||
await getUsersCollection()
|
||||
.find({ name })
|
||||
.collation({ locale: "en", strength: 1 })
|
||||
.limit(1)
|
||||
.toArray()
|
||||
)[0];
|
||||
}
|
||||
|
||||
export async function isNameAvailable(name: string): Promise<boolean> {
|
||||
return (await findByName(name)) === undefined;
|
||||
}
|
||||
|
||||
export async function getUserByName(
|
||||
name: string,
|
||||
stack: string
|
||||
): Promise<MonkeyTypes.User> {
|
||||
const user = await findByName(name);
|
||||
if (!user) throw new MonkeyError(404, "User not found", stack);
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function isDiscordIdAvailable(
|
||||
discordId: string
|
||||
): Promise<boolean> {
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ key {
|
|||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
transition: background 0.125s, color 0.125s;
|
||||
padding: 0.5rem;
|
||||
padding: 0.5em;
|
||||
border-radius: var(--roundness);
|
||||
background: var(--sub-alt-color);
|
||||
text-align: center;
|
||||
|
|
@ -315,11 +315,11 @@ key {
|
|||
align-content: center;
|
||||
height: min-content;
|
||||
height: -moz-min-content;
|
||||
line-height: 1.25rem;
|
||||
line-height: 1.25em;
|
||||
appearance: none;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
font-size: 1em;
|
||||
|
||||
&.active {
|
||||
background: var(--main-color);
|
||||
|
|
@ -454,15 +454,15 @@ key {
|
|||
}
|
||||
position: relative;
|
||||
.statusIndicator {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25em;
|
||||
height: 2.25em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
/* background: red; */
|
||||
display: grid;
|
||||
grid-template-columns: 2.25rem;
|
||||
grid-template-rows: 2.25rem;
|
||||
grid-template-columns: 2.25em;
|
||||
grid-template-rows: 2.25em;
|
||||
place-items: center center;
|
||||
cursor: pointer;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ textarea {
|
|||
border-radius: var(--roundness);
|
||||
background: var(--sub-alt-color);
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
padding: 0.5em;
|
||||
font-size: 1em;
|
||||
font-family: var(--font);
|
||||
caret-color: var(--main-color);
|
||||
line-height: 1.25em;
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,26 @@
|
|||
.pageProfileSearch {
|
||||
align-content: center;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
.search {
|
||||
justify-self: center;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 1rem;
|
||||
font-size: 1.25rem;
|
||||
.title {
|
||||
font-size: 1.25em;
|
||||
grid-column: span 2;
|
||||
color: var(--sub-color);
|
||||
}
|
||||
.button {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pageProfile {
|
||||
align-content: center;
|
||||
height: 100%;
|
||||
|
|
@ -14,6 +37,21 @@
|
|||
transform: translate(-50%, -50%);
|
||||
color: var(--main-color);
|
||||
}
|
||||
.error {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
font-size: 2rem;
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--roundness);
|
||||
padding: 2rem;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: var(--error-color);
|
||||
.message {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profile {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@
|
|||
|
||||
.fas {
|
||||
margin-right: 0rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,8 +183,12 @@ export default class Users {
|
|||
});
|
||||
}
|
||||
|
||||
async getProfile(uid: string): Promise<Ape.EndpointData> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/${uid}/profile`);
|
||||
async getProfileByUid(uid: string): Promise<Ape.EndpointData> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/${uid}/profile?isUid`);
|
||||
}
|
||||
|
||||
async getProfileByName(name: string): Promise<Ape.EndpointData> {
|
||||
return await this.httpClient.get(`${BASE_PATH}/${name}/profile`);
|
||||
}
|
||||
|
||||
async updateProfile(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { navigate } from "../../controllers/route-controller";
|
||||
import { navigate } from "../../observables/navigate-event";
|
||||
import * as ChallengeController from "../../controllers/challenge-controller";
|
||||
import * as TestLogic from "../../test/test-logic";
|
||||
import { capitalizeFirstLetterOfEachWord } from "../../utils/misc";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { navigate } from "../../controllers/route-controller";
|
||||
import { navigate } from "../../observables/navigate-event";
|
||||
import { toggleFullscreen } from "../../utils/misc";
|
||||
|
||||
const commands: MonkeyTypes.Command[] = [
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import {
|
|||
hideFavoriteQuoteLength,
|
||||
showFavoriteQuoteLength,
|
||||
} from "../test/test-config";
|
||||
import { navigate } from "./route-controller";
|
||||
import { navigate } from "../observables/navigate-event";
|
||||
import { update as updateTagsCommands } from "../commandline/lists/tags";
|
||||
|
||||
export const gmailProvider = new GoogleAuthProvider();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import * as CompositionState from "../states/composition";
|
|||
import * as TestInput from "../test/test-input";
|
||||
import * as TestWords from "../test/test-words";
|
||||
import * as Hangul from "hangul-js";
|
||||
import { navigate } from "./route-controller";
|
||||
import { navigate } from "../observables/navigate-event";
|
||||
|
||||
let dontInsertSpace = false;
|
||||
let correctShiftUsed = true;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import * as PageAbout from "../pages/about";
|
|||
import * as PageLogin from "../pages/login";
|
||||
import * as PageLoading from "../pages/loading";
|
||||
import * as PageProfile from "../pages/profile";
|
||||
import * as PageProfileSearch from "../pages/profile-search";
|
||||
import * as Page404 from "../pages/404";
|
||||
import * as PageTransition from "../states/page-transition";
|
||||
import type Page from "../pages/page";
|
||||
|
|
@ -16,6 +17,7 @@ import * as Focus from "../test/focus";
|
|||
interface ChangeOptions {
|
||||
force?: boolean;
|
||||
params?: { [key: string]: string };
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export async function change(
|
||||
|
|
@ -48,6 +50,7 @@ export async function change(
|
|||
account: Account.page,
|
||||
login: PageLogin.page,
|
||||
profile: PageProfile.page,
|
||||
profileSearch: PageProfileSearch.page,
|
||||
404: Page404.page,
|
||||
};
|
||||
|
||||
|
|
@ -72,7 +75,10 @@ export async function change(
|
|||
Focus.set(false);
|
||||
ActivePage.set(nextPage.name);
|
||||
previousPage?.afterHide();
|
||||
await nextPage?.beforeShow(options.params);
|
||||
await nextPage?.beforeShow({
|
||||
params: options.params,
|
||||
data: options.data,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import * as PageAccount from "../pages/account";
|
|||
import * as PageLogin from "../pages/login";
|
||||
import * as Page404 from "../pages/404";
|
||||
import * as PageProfile from "../pages/profile";
|
||||
import * as PageProfileSearch from "../pages/profile-search";
|
||||
import * as Leaderboards from "../elements/leaderboards";
|
||||
import * as TestUI from "../test/test-ui";
|
||||
import * as PageTransition from "../states/page-transition";
|
||||
import { Auth } from "../firebase";
|
||||
import * as NavigateEvent from "../observables/navigate-event";
|
||||
|
||||
//source: https://www.youtube.com/watch?v=OstALBk-jTc
|
||||
// https://www.youtube.com/watch?v=OstALBk-jTc
|
||||
|
|
@ -17,6 +18,7 @@ import { Auth } from "../firebase";
|
|||
//this will be used in tribe
|
||||
interface NavigateOptions {
|
||||
empty?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
function pathToRegex(path: string): RegExp {
|
||||
|
|
@ -92,27 +94,26 @@ const routes: Route[] = [
|
|||
},
|
||||
{
|
||||
path: "/profile",
|
||||
load: (): void => {
|
||||
if (Auth.currentUser) {
|
||||
navigate("/account");
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
load: (_params): void => {
|
||||
PageController.change(PageProfileSearch.page);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/profile/:uid",
|
||||
load: (params): void => {
|
||||
path: "/profile/:uidOrName",
|
||||
load: (params, options): void => {
|
||||
PageController.change(PageProfile.page, {
|
||||
force: true,
|
||||
params,
|
||||
params: {
|
||||
uidOrName: params["uidOrName"],
|
||||
},
|
||||
data: options.data,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function navigate(
|
||||
url = window.location.pathname,
|
||||
function nav(
|
||||
url = window.location.pathname + window.location.search,
|
||||
options = {} as NavigateOptions
|
||||
): void {
|
||||
if (
|
||||
|
|
@ -158,15 +159,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
const target = e?.target as HTMLLinkElement;
|
||||
if (target.matches("[router-link]") && target?.href) {
|
||||
e.preventDefault();
|
||||
navigate(target.href);
|
||||
nav(target.href);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("#top .logo").on("click", () => {
|
||||
navigate("/");
|
||||
nav("/");
|
||||
});
|
||||
|
||||
$(document).on("click", "#leaderboards a.entryName", () => {
|
||||
Leaderboards.hide();
|
||||
});
|
||||
|
||||
NavigateEvent.subscribe((url, options) => {
|
||||
nav(url, options);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ export class InputIndicator {
|
|||
hide(): void {
|
||||
this.parentElement.find(".statusIndicator div").addClass("hidden");
|
||||
this.currentStatus = null;
|
||||
$(this.inputElement).css("padding-right", "0.5rem");
|
||||
$(this.inputElement).css("padding-right", "0.5em");
|
||||
}
|
||||
|
||||
show(optionId: keyof typeof this.options, messageOverride?: string): void {
|
||||
|
|
@ -74,7 +74,7 @@ export class InputIndicator {
|
|||
indicator.attr("aria-label", messageOverride);
|
||||
}
|
||||
|
||||
$(this.inputElement).css("padding-right", "2.1rem");
|
||||
$(this.inputElement).css("padding-right", "2.1em");
|
||||
}
|
||||
|
||||
get(): keyof typeof this.options | null {
|
||||
|
|
|
|||
|
|
@ -322,9 +322,9 @@ async function fillTable(lb: LbKey, prepend?: number): Promise<void> {
|
|||
}</td>
|
||||
<td>
|
||||
<div class="avatarNameBadge">${avatar}
|
||||
<a href="${location.origin}/profile/${entry.uid}" class="entryName" uid=${
|
||||
<a href="${location.origin}/profile/${
|
||||
entry.uid
|
||||
} router-link>${entry.name}</a>
|
||||
}?isUid" class="entryName" uid=${entry.uid} router-link>${entry.name}</a>
|
||||
${entry.badgeId ? getBadgeHTMLbyId(entry.badgeId) : ""}
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import "./popups/edit-preset-popup";
|
|||
import "./popups/simple-popups";
|
||||
import "./controllers/input-controller";
|
||||
import "./ready";
|
||||
import "./controllers/route-controller";
|
||||
import "./pages/about";
|
||||
import "./popups/pb-tables-popup";
|
||||
import "./elements/scroll-to-top";
|
||||
|
|
|
|||
23
frontend/src/ts/observables/navigate-event.ts
Normal file
23
frontend/src/ts/observables/navigate-event.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
type SubscribeFunction = (url?: string, options?: NavigateOptions) => void;
|
||||
|
||||
const subscribers: SubscribeFunction[] = [];
|
||||
|
||||
export function subscribe(fn: SubscribeFunction): void {
|
||||
subscribers.push(fn);
|
||||
}
|
||||
|
||||
interface NavigateOptions {
|
||||
empty?: boolean;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export function navigate(url?: string, options?: NavigateOptions): void {
|
||||
subscribers.forEach((fn) => {
|
||||
try {
|
||||
fn(url, options);
|
||||
} catch (e) {
|
||||
console.error("Navigate event subscriber threw an error");
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1110,7 +1110,7 @@ $(".pageAccount .content .group.aboveHistory .exportCSV").on("click", () => {
|
|||
});
|
||||
|
||||
$(document).on("click", ".pageAccount .profile .details .copyLink", () => {
|
||||
const url = `${location.origin}/profile/${Auth.currentUser?.uid}`;
|
||||
const url = `${location.origin}/profile/${Auth.currentUser?.uid}?isUid`;
|
||||
|
||||
navigator.clipboard.writeText(url).then(
|
||||
function () {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
interface Options {
|
||||
params?: Record<string, string>;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export default class Page {
|
||||
public name: string;
|
||||
public element: JQuery;
|
||||
public pathname: string;
|
||||
public beforeHide: () => Promise<void>;
|
||||
public afterHide: () => Promise<void>;
|
||||
public beforeShow: (params?: { [key: string]: string }) => Promise<void>;
|
||||
public beforeShow: (options: Options) => Promise<void>;
|
||||
public afterShow: () => Promise<void>;
|
||||
constructor(
|
||||
name: string,
|
||||
|
|
@ -12,7 +17,7 @@ export default class Page {
|
|||
pathname: string,
|
||||
beforeHide: () => Promise<void>,
|
||||
afterHide: () => Promise<void>,
|
||||
beforeShow: (params?: { [key: string]: string }) => Promise<void>,
|
||||
beforeShow: (options: Options) => Promise<void>,
|
||||
afterShow: () => Promise<void>
|
||||
) {
|
||||
this.name = name;
|
||||
|
|
|
|||
92
frontend/src/ts/pages/profile-search.ts
Normal file
92
frontend/src/ts/pages/profile-search.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import Page from "./page";
|
||||
import { InputIndicator } from "../elements/input-indicator";
|
||||
import { sleep } from "../utils/misc";
|
||||
import Ape from "../ape";
|
||||
import { navigate } from "../observables/navigate-event";
|
||||
|
||||
const searchIndicator = new InputIndicator(
|
||||
$(".page.pageProfileSearch .search input"),
|
||||
{
|
||||
notFound: {
|
||||
icon: "fa-user-slash",
|
||||
level: -1,
|
||||
},
|
||||
error: {
|
||||
icon: "fa-times",
|
||||
level: -1,
|
||||
},
|
||||
checking: {
|
||||
icon: "fa-circle-notch",
|
||||
spinIcon: true,
|
||||
level: 1,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
function disableInputs(): void {
|
||||
$(".page.pageProfileSearch .search .button").addClass("disabled");
|
||||
$(".page.pageProfileSearch .search input").attr("disabled", "disabled");
|
||||
}
|
||||
|
||||
function enableInputs(): void {
|
||||
$(".page.pageProfileSearch .search .button").removeClass("disabled");
|
||||
$(".page.pageProfileSearch .search input").removeAttr("disabled");
|
||||
}
|
||||
|
||||
function areInputsDisabled(): boolean {
|
||||
return (
|
||||
$(".page.pageProfileSearch .search input").attr("disabled") !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
async function lookupProfile(): Promise<void> {
|
||||
searchIndicator.hide();
|
||||
const name = $(".page.pageProfileSearch .search input").val() as string;
|
||||
if (name === "") return;
|
||||
|
||||
searchIndicator.show("checking");
|
||||
disableInputs();
|
||||
|
||||
await sleep(500);
|
||||
|
||||
const response = await Ape.users.getProfileByName(name);
|
||||
enableInputs();
|
||||
if (response.status === 404) {
|
||||
searchIndicator.show("notFound", "User not found");
|
||||
return;
|
||||
} else if (response.status !== 200) {
|
||||
searchIndicator.show("error", `Error: ${response.message}`);
|
||||
return;
|
||||
}
|
||||
navigate(`/profile/${name}`, {
|
||||
data: response.data,
|
||||
});
|
||||
}
|
||||
|
||||
$(".page.pageProfileSearch .search input").on("keyup", (e) => {
|
||||
if (e.key === "Enter" && !areInputsDisabled()) lookupProfile();
|
||||
});
|
||||
|
||||
$(".page.pageProfileSearch .search .button").on("click", () => {
|
||||
if (areInputsDisabled()) return;
|
||||
lookupProfile();
|
||||
});
|
||||
|
||||
export const page = new Page(
|
||||
"profileSearch",
|
||||
$(".page.pageProfileSearch"),
|
||||
"/profile",
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
//
|
||||
},
|
||||
async () => {
|
||||
$(".page.pageProfileSearch input").val("");
|
||||
searchIndicator.hide();
|
||||
},
|
||||
async () => {
|
||||
$(".page.pageProfileSearch input").focus();
|
||||
}
|
||||
);
|
||||
|
|
@ -3,6 +3,7 @@ import Page from "./page";
|
|||
import * as Profile from "../elements/profile";
|
||||
import * as PbTables from "../account/pb-tables";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import { checkIfGetParameterExists } from "../utils/misc";
|
||||
|
||||
function reset(): void {
|
||||
$(".page.pageProfile .preloader").removeClass("hidden");
|
||||
|
|
@ -126,17 +127,45 @@ function reset(): void {
|
|||
</div>`);
|
||||
}
|
||||
|
||||
async function update(userId: string): Promise<void> {
|
||||
const response = await Ape.users.getProfile(userId ?? "");
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
interface UpdateOptions {
|
||||
uidOrName?: string;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
if (response.status !== 200) {
|
||||
// $(".page.pageProfile .failedToLoad").removeClass("hidden");
|
||||
return Notifications.add("Failed to load profile: " + response.message, -1);
|
||||
async function update(options: UpdateOptions): Promise<void> {
|
||||
const getParamExists = checkIfGetParameterExists("isUid");
|
||||
if (options.data) {
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
Profile.update("profile", options.data);
|
||||
PbTables.update(options.data.personalBests, true);
|
||||
} else if (options.uidOrName) {
|
||||
const response =
|
||||
getParamExists === true
|
||||
? await Ape.users.getProfileByUid(options.uidOrName)
|
||||
: await Ape.users.getProfileByName(options.uidOrName);
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
|
||||
if (response.status === 404) {
|
||||
const message =
|
||||
getParamExists === true
|
||||
? "User not found"
|
||||
: `User ${options.uidOrName} not found`;
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
$(".page.pageProfile .error").removeClass("hidden");
|
||||
$(".page.pageProfile .error .message").text(message);
|
||||
} else if (response.status !== 200) {
|
||||
// $(".page.pageProfile .failedToLoad").removeClass("hidden");
|
||||
return Notifications.add(
|
||||
"Failed to load profile: " + response.message,
|
||||
-1
|
||||
);
|
||||
}
|
||||
|
||||
Profile.update("profile", response.data);
|
||||
PbTables.update(response.data.personalBests, true);
|
||||
} else {
|
||||
Notifications.add("Missing update parameter!", -1);
|
||||
}
|
||||
|
||||
Profile.update("profile", response.data);
|
||||
PbTables.update(response.data.personalBests, true);
|
||||
}
|
||||
|
||||
export const page = new Page(
|
||||
|
|
@ -149,9 +178,22 @@ export const page = new Page(
|
|||
async () => {
|
||||
reset();
|
||||
},
|
||||
async (params) => {
|
||||
reset();
|
||||
update(params?.["uid"] ?? "");
|
||||
async (options) => {
|
||||
const uidOrName = options?.params?.["uidOrName"];
|
||||
if (uidOrName) {
|
||||
$(".page.pageProfile .preloader").removeClass("hidden");
|
||||
$(".page.pageProfile .search").addClass("hidden");
|
||||
$(".page.pageProfile .content").removeClass("hidden");
|
||||
reset();
|
||||
update({
|
||||
uidOrName,
|
||||
data: options?.["data"],
|
||||
});
|
||||
} else {
|
||||
$(".page.pageProfile .preloader").addClass("hidden");
|
||||
$(".page.pageProfile .search").removeClass("hidden");
|
||||
$(".page.pageProfile .content").addClass("hidden");
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
//
|
||||
|
|
|
|||
|
|
@ -697,6 +697,28 @@ export function findGetParameter(
|
|||
return result;
|
||||
}
|
||||
|
||||
export function checkIfGetParameterExists(
|
||||
parameterName: string,
|
||||
getOverride?: string
|
||||
): boolean {
|
||||
let result = false;
|
||||
let tmp = [];
|
||||
|
||||
let search = location.search;
|
||||
if (getOverride) {
|
||||
search = getOverride;
|
||||
}
|
||||
|
||||
search
|
||||
.substr(1)
|
||||
.split("&")
|
||||
.forEach(function (item) {
|
||||
tmp = item.split("=");
|
||||
if (tmp[0] === parameterName) result = true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function objectToQueryString<T extends string | number | boolean>(
|
||||
obj: Record<string, T | T[]>
|
||||
): string {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
<div class="page pageProfileSearch hidden">
|
||||
<div class="search">
|
||||
<div class="title">Profile lookup</div>
|
||||
<input class="username" type="text" placeholder="username" />
|
||||
<div class="button">
|
||||
<i class="fas fa-fw fa-chevron-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page pageProfile hidden">
|
||||
<div class="content">
|
||||
<div class="preloader">
|
||||
<i class="fas fa-fw fa-spin fa-circle-notch"></i>
|
||||
</div>
|
||||
<div class="error hidden">
|
||||
<i class="fas fa-times"></i>
|
||||
<div class="message"></div>
|
||||
</div>
|
||||
<div class="profile">
|
||||
<div class="details none">
|
||||
<div class="avatarAndName">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue