feat: add /users/@me endpoint, add edit button to profile page

This commit is contained in:
Sam 2022-05-13 16:43:28 +02:00
parent d2f4e09a01
commit 15797b679c
8 changed files with 115 additions and 40 deletions

View file

@ -90,3 +90,27 @@ func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error {
render.JSON(w, r, dbUserToResponse(u, fields)) render.JSON(w, r, dbUserToResponse(u, fields))
return nil return nil
} }
func (s *Server) getMeUser(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
u, err := s.DB.User(ctx, claims.UserID)
if err != nil {
log.Errorf("Error getting user: %v", err)
return err
}
fields, err := s.DB.UserFields(ctx, u.ID)
if err != nil {
log.Errorf("Error getting user fields: %v", err)
return err
}
render.JSON(w, r, GetMeResponse{
GetUserResponse: dbUserToResponse(u, fields),
Discord: u.Discord,
DiscordUsername: u.DiscordUsername,
})
return nil
}

View file

@ -13,7 +13,7 @@ func Mount(srv *server.Server, r chi.Router) {
s := &Server{srv} s := &Server{srv}
r.Route("/users", func(r chi.Router) { r.Route("/users", func(r chi.Router) {
r.With(server.MustAuth).Get("/@me", server.WrapHandler(nil)) r.With(server.MustAuth).Get("/@me", server.WrapHandler(s.getMeUser))
r.Get("/{userRef}", server.WrapHandler(s.getUser)) r.Get("/{userRef}", server.WrapHandler(s.getUser))
}) })

14
frontend/src/lib/Card.tsx Normal file
View file

@ -0,0 +1,14 @@
import React, { PropsWithChildren } from "react";
export type Props = PropsWithChildren<{ title: string }>;
export default function Card({ title, children }: Props) {
return (
<div className="bg-slate-100 dark:bg-slate-700 rounded-md shadow">
<h1 className="text-2xl p-2 border-b border-zinc-200 dark:border-slate-800">
{title}
</h1>
<div className="flex flex-col p-2">{children}</div>
</div>
);
}

View file

@ -6,39 +6,37 @@ import {
EmojiLaughing, EmojiLaughing,
} from "react-bootstrap-icons"; } from "react-bootstrap-icons";
import Card from "./Card";
import type { Field } from "./types"; import type { Field } from "./types";
export default function FieldCard({ field }: { field: Field }) { export default function FieldCard({ field }: { field: Field }) {
return ( return (
<div className=" bg-slate-100 dark:bg-slate-700 rounded-md shadow"> <Card title={field.name}>
<h1 className="text-2xl p-2 border-b border-zinc-200 dark:border-slate-800">{field.name}</h1> {field.favourite.map((entry) => (
<div className="flex flex-col p-2"> <p className="text-lg font-bold">
{field.favourite.map((entry) => ( <HeartFill className="inline" /> {entry}
<p className="text-lg font-bold"> </p>
<HeartFill className="inline" /> {entry} ))}
</p> {field.okay.length !== 0 && (
))} <p>
{field.okay.length !== 0 && ( <HandThumbsUp className="inline" /> {field.okay.join(", ")}
<p> </p>
<HandThumbsUp className="inline" /> {field.okay.join(", ")} )}
</p> {field.jokingly.length !== 0 && (
)} <p>
{field.jokingly.length !== 0 && ( <EmojiLaughing className="inline" /> {field.jokingly.join(", ")}
<p> </p>
<EmojiLaughing className="inline" /> {field.jokingly.join(", ")} )}
</p> {field.friends_only.length !== 0 && (
)} <p>
{field.friends_only.length !== 0 && ( <People className="inline" /> {field.friends_only.join(", ")}
<p> </p>
<People className="inline" /> {field.friends_only.join(", ")} )}
</p> {field.avoid.length !== 0 && (
)} <p className="text-slate-600 dark:text-slate-400">
{field.avoid.length !== 0 && ( <HandThumbsDown className="inline" /> {field.avoid.join(", ")}
<p className="text-slate-600 dark:text-slate-400"> </p>
<HandThumbsDown className="inline" /> {field.avoid.join(", ")} )}
</p> </Card>
)}
</div>
</div>
); );
} }

View file

@ -45,7 +45,7 @@ function Navigation() {
const nav = user ? ( const nav = user ? (
<> <>
<NavItem to="/me">@{user.username}</NavItem> <NavItem to={`/u/${user.username}`}>@{user.username}</NavItem>
<NavItem to="/settings">Settings</NavItem> <NavItem to="/settings">Settings</NavItem>
<NavItem to="/logout">Log out</NavItem> <NavItem to="/logout">Log out</NavItem>
</> </>

View file

@ -27,3 +27,8 @@ async function getCurrentUser() {
return null; return null;
} }
export function isMeUser(id: string): boolean {
const meUser = useRecoilValue(userState);
return meUser && meUser.id === id;
}

View file

@ -1,17 +1,22 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { ArrowClockwise } from "react-bootstrap-icons"; import { ArrowClockwise } from "react-bootstrap-icons";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import type { APIError, User } from "../lib/types"; import NavItem from "../lib/NavItem";
import type { User } from "../lib/types";
import fetchAPI from "../lib/fetch"; import fetchAPI from "../lib/fetch";
import FieldCard from "../lib/FieldCard"; import FieldCard from "../lib/FieldCard";
import Card from "../lib/Card";
import { userState } from "../lib/store";
import { useRecoilValue } from "recoil";
function UserPage() { function UserPage() {
const params = useParams(); const params = useParams();
const [user, setUser] = useState<User>(null); const [user, setUser] = useState<User>(null);
const meUser = useRecoilValue(userState);
useEffect(() => { useEffect(() => {
fetchAPI<User>(`/users/${params.username}`).then((res) => { fetchAPI<User>(`/users/${params.username}`).then((res) => {
@ -33,13 +38,25 @@ function UserPage() {
<Helmet> <Helmet>
<title>@{user.username} - pronouns.cc</title> <title>@{user.username} - pronouns.cc</title>
</Helmet> </Helmet>
{meUser && meUser.id === user.id && (
<div className="lg:w-1/3 mx-auto bg-slate-100 dark:bg-slate-700 shadow rounded-md p-2">
<span>
You are currently viewing your{" "}
<span className="font-bold">public</span> profile.
</span>
<br />
<Link
to="/edit"
className="hover:underline text-sky-500 dark:text-sky-400"
>
Edit your profile
</Link>
</div>
)}
<div className="container mx-auto"> <div className="container mx-auto">
<div className="flex flex-col lg:flex-row justify-center lg:justify-start items-center space-y-4 lg:space-y-0 lg:space-x-16 lg:items-start"> <div className="flex flex-col m-2 p-2 lg:flex-row justify-center lg:justify-start items-center space-y-4 lg:space-y-0 lg:space-x-16 lg:items-start border-b border-slate-200 dark:border-slate-700">
{user.avatar_url && ( {user.avatar_url && (
<img <img className="max-w-xs rounded-full" src={user.avatar_url} />
className="max-w-max lg:max-w-lg rounded-full"
src={user.avatar_url}
/>
)} )}
<div className="flex flex-col lg:mx-auto"> <div className="flex flex-col lg:mx-auto">
{user.display_name && ( {user.display_name && (
@ -59,7 +76,7 @@ function UserPage() {
{user.bio} {user.bio}
</ReactMarkdown> </ReactMarkdown>
)} )}
{user.links.length !== 0 && ( {user.links.length !== 0 && user.fields.length === 0 && (
<div className="flex flex-col mx-auto lg:ml-auto"> <div className="flex flex-col mx-auto lg:ml-auto">
{user.links.map((link) => ( {user.links.map((link) => (
<a <a
@ -78,6 +95,19 @@ function UserPage() {
{user.fields.map((field) => ( {user.fields.map((field) => (
<FieldCard field={field}></FieldCard> <FieldCard field={field}></FieldCard>
))} ))}
{user.links.length !== 0 && (
<Card title="Links">
{user.links.map((link) => (
<a
href={link}
rel="nofollow noopener noreferrer me"
className="hover:underline text-sky-500 dark:text-sky-400"
>
{link}
</a>
))}
</Card>
)}
</div> </div>
</div> </div>
</> </>

View file

@ -67,5 +67,9 @@ export default function Discord() {
navigate("/"); navigate("/");
} }
if (user || state.isLoading) {
return <>Loading...</>;
}
return <>wow such login</>; return <>wow such login</>;
} }