chore: update user profile page

This commit is contained in:
Steven
2023-09-10 10:33:22 +08:00
parent 44be7201c0
commit 3df550927d
24 changed files with 74 additions and 195 deletions

View File

@ -2687,9 +2687,6 @@ const docTemplate = `{
"v1.CreateResourceRequest": { "v1.CreateResourceRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
"downloadToLocal": {
"type": "boolean"
},
"externalLink": { "externalLink": {
"type": "string" "type": "string"
}, },

View File

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"mime"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -50,11 +49,10 @@ type Resource struct {
} }
type CreateResourceRequest struct { type CreateResourceRequest struct {
Filename string `json:"filename"` Filename string `json:"filename"`
InternalPath string `json:"internalPath"` InternalPath string `json:"internalPath"`
ExternalLink string `json:"externalLink"` ExternalLink string `json:"externalLink"`
Type string `json:"type"` Type string `json:"type"`
DownloadToLocal bool `json:"downloadToLocal"`
} }
type FindResourceRequest struct { type FindResourceRequest struct {
@ -172,41 +170,6 @@ func (s *APIV1Service) CreateResource(c echo.Context) error {
if linkURL.Scheme != "http" && linkURL.Scheme != "https" { if linkURL.Scheme != "http" && linkURL.Scheme != "https" {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme") return echo.NewHTTPError(http.StatusBadRequest, "Invalid external link scheme")
} }
if request.DownloadToLocal {
resp, err := http.Get(linkURL.String())
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to request %s", request.ExternalLink))
}
defer resp.Body.Close()
blob, err := io.ReadAll(resp.Body)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to read %s", request.ExternalLink))
}
mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Failed to read mime from %s", request.ExternalLink))
}
create.Type = mediaType
filename := path.Base(linkURL.Path)
if path.Ext(filename) == "" {
extensions, _ := mime.ExtensionsByType(mediaType)
if len(extensions) > 0 {
filename += extensions[0]
}
}
create.Filename = filename
create.ExternalLink = ""
create.Size = int64(len(blob))
err = SaveResourceBlob(ctx, s.Store, create, bytes.NewReader(blob))
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to save resource").SetInternal(err)
}
}
} }
resource, err := s.Store.CreateResource(ctx, create) resource, err := s.Store.CreateResource(ctx, create)

View File

@ -265,8 +265,6 @@ definitions:
type: object type: object
v1.CreateResourceRequest: v1.CreateResourceRequest:
properties: properties:
downloadToLocal:
type: boolean
externalLink: externalLink:
type: string type: string
filename: filename:

View File

@ -13,7 +13,7 @@ interface Props extends DialogProps {
onConfirm?: (resourceList: Resource[]) => void; onConfirm?: (resourceList: Resource[]) => void;
} }
type SelectedMode = "local-file" | "external-link" | "download-link"; type SelectedMode = "local-file" | "external-link";
interface State { interface State {
selectedMode: SelectedMode; selectedMode: SelectedMode;
@ -32,7 +32,6 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
filename: "", filename: "",
externalLink: "", externalLink: "",
type: "", type: "",
downloadToLocal: false,
}); });
const [fileList, setFileList] = useState<File[]>([]); const [fileList, setFileList] = useState<File[]>([]);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@ -71,7 +70,7 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
destroy(); destroy();
}; };
const handleSelectedModeChanged = (mode: "local-file" | "external-link" | "download-link") => { const handleSelectedModeChanged = (mode: "local-file" | "external-link") => {
setState((state) => { setState((state) => {
return { return {
...state, ...state,
@ -130,10 +129,6 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
if (resourceCreate.filename === "" || resourceCreate.externalLink === "" || resourceCreate.type === "") { if (resourceCreate.filename === "" || resourceCreate.externalLink === "" || resourceCreate.type === "") {
return false; return false;
} }
} else if (state.selectedMode === "download-link") {
if (resourceCreate.externalLink === "") {
return false;
}
} }
return true; return true;
}; };
@ -166,9 +161,6 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
createdResourceList.push(resource); createdResourceList.push(resource);
} }
} else { } else {
if (state.selectedMode === "download-link") {
resourceCreate.downloadToLocal = true;
}
const resource = await resourceStore.createResource(resourceCreate); const resource = await resourceStore.createResource(resourceCreate);
createdResourceList.push(resource); createdResourceList.push(resource);
} }
@ -203,7 +195,6 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
> >
<Option value="local-file">{t("resource.create-dialog.local-file.option")}</Option> <Option value="local-file">{t("resource.create-dialog.local-file.option")}</Option>
<Option value="external-link">{t("resource.create-dialog.external-link.option")}</Option> <Option value="external-link">{t("resource.create-dialog.external-link.option")}</Option>
<Option value="download-link">{t("resource.create-dialog.download-link.option")}</Option>
</Select> </Select>
{state.selectedMode === "local-file" && ( {state.selectedMode === "local-file" && (
@ -288,21 +279,6 @@ const CreateResourceDialog: React.FC<Props> = (props: Props) => {
</> </>
)} )}
{state.selectedMode === "download-link" && (
<>
<Typography className="!mb-1" level="body-md">
{t("resource.create-dialog.external-link.link")}
</Typography>
<Input
className="mb-2"
placeholder={t("resource.create-dialog.external-link.link-placeholder")}
value={resourceCreate.externalLink}
onChange={handleExternalLinkChanged}
fullWidth
/>
</>
)}
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1"> <div className="mt-2 w-full flex flex-row justify-end items-center space-x-1">
<Button variant="plain" color="neutral" onClick={handleCloseDialog}> <Button variant="plain" color="neutral" onClick={handleCloseDialog}>
{t("common.cancel")} {t("common.cancel")}

View File

@ -216,17 +216,15 @@ const Memo: React.FC<Props> = (props: Props) => {
}; };
const handleMemoCreatedTimeClick = (e: React.MouseEvent) => { const handleMemoCreatedTimeClick = (e: React.MouseEvent) => {
if (e.altKey) { e.preventDefault();
e.preventDefault(); showChangeMemoCreatedTsDialog(memo.id);
showChangeMemoCreatedTsDialog(memo.id);
}
}; };
return ( return (
<> <>
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}> <div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}>
<div className="memo-top-wrapper"> <div className="memo-top-wrapper">
<p className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1"> <div className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1">
{creator && ( {creator && (
<> <>
<Link className="flex flex-row justify-start items-center" to={`/u/${memo.creatorUsername}`}> <Link className="flex flex-row justify-start items-center" to={`/u/${memo.creatorUsername}`}>
@ -236,10 +234,10 @@ const Memo: React.FC<Props> = (props: Props) => {
<Icon.Dot className="w-4 h-auto text-gray-400 dark:text-zinc-400" /> <Icon.Dot className="w-4 h-auto text-gray-400 dark:text-zinc-400" />
</> </>
)} )}
<span className="text-sm text-gray-400" onClick={handleMemoCreatedTimeClick}> <span className="text-sm text-gray-400 select-none" onDoubleClick={handleMemoCreatedTimeClick}>
{displayTime} {displayTime}
</span> </span>
</p> </div>
<div className="btns-container space-x-2"> <div className="btns-container space-x-2">
{!readonly && ( {!readonly && (
<> <>

View File

@ -1,4 +1,4 @@
import { Divider, Option, Select } from "@mui/joy"; import { Divider, IconButton, Radio, RadioGroup } from "@mui/joy";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import * as api from "@/helpers/api"; import * as api from "@/helpers/api";
@ -6,6 +6,7 @@ import { useGlobalStore } from "@/store/module";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import showCreateStorageServiceDialog from "../CreateStorageServiceDialog"; import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
import { showCommonDialog } from "../Dialog/CommonDialog"; import { showCommonDialog } from "../Dialog/CommonDialog";
import Icon from "../Icon";
import LearnMore from "../LearnMore"; import LearnMore from "../LearnMore";
import showUpdateLocalStorageDialog from "../UpdateLocalStorageDialog"; import showUpdateLocalStorageDialog from "../UpdateLocalStorageDialog";
import Dropdown from "../kit/Dropdown"; import Dropdown from "../kit/Dropdown";
@ -58,23 +59,26 @@ const StorageSection = () => {
<div className="mt-4 mb-2 w-full flex flex-row justify-start items-center"> <div className="mt-4 mb-2 w-full flex flex-row justify-start items-center">
<span className="font-mono text-sm text-gray-400 mr-2">{t("setting.storage-section.current-storage")}</span> <span className="font-mono text-sm text-gray-400 mr-2">{t("setting.storage-section.current-storage")}</span>
</div> </div>
<Select <RadioGroup
className="w-full mb-4" className="w-full"
value={storageServiceId} value={storageServiceId}
onChange={(_, storageId) => { onChange={(event) => {
handleActiveStorageServiceChanged(storageId ?? storageServiceId); handleActiveStorageServiceChanged(Number(event.target.value));
}} }}
> >
<Option value={0}>{t("setting.storage-section.type-database")}</Option> <div className="w-full flex flex-row justify-start items-center gap-x-2">
<Option value={-1}>{t("setting.storage-section.type-local")}</Option> <Radio value={"-1"} label={t("setting.storage-section.type-local")} />
<IconButton size="sm" onClick={() => showUpdateLocalStorageDialog(systemStatus.localStoragePath)}>
<Icon.PenBox className="w-4 h-auto" />
</IconButton>
</div>
<Radio value={"0"} label={t("setting.storage-section.type-database")} />
{storageList.map((storage) => ( {storageList.map((storage) => (
<Option key={storage.id} value={storage.id}> <Radio key={storage.id} value={storage.id} label={storage.name} />
{storage.name}
</Option>
))} ))}
</Select> </RadioGroup>
<Divider /> <Divider className="!my-4" />
<div className="mt-4 mb-2 w-full flex flex-row justify-start items-center gap-1"> <div className="mb-2 w-full flex flex-row justify-start items-center gap-1">
<span className="font-mono text-sm text-gray-400">{t("setting.storage-section.storage-services-list")}</span> <span className="font-mono text-sm text-gray-400">{t("setting.storage-section.storage-services-list")}</span>
<LearnMore url="https://usememos.com/docs/storage" /> <LearnMore url="https://usememos.com/docs/storage" />
<button className="btn-normal px-2 py-0 ml-1" onClick={() => showCreateStorageServiceDialog(undefined, fetchStorageList)}> <button className="btn-normal px-2 py-0 ml-1" onClick={() => showCreateStorageServiceDialog(undefined, fetchStorageList)}>
@ -82,30 +86,6 @@ const StorageSection = () => {
</button> </button>
</div> </div>
<div className="mt-2 w-full flex flex-col"> <div className="mt-2 w-full flex flex-col">
<div
className={
storageServiceId !== -1 ? "hidden" : "py-2 w-full border-t dark:border-zinc-700 flex flex-row items-center justify-between"
}
>
<div className="flex flex-row items-center">
<p className="ml-2">{t("setting.storage-section.type-local")}</p>
</div>
<div className="flex flex-row items-center">
<Dropdown
actionsClassName="!w-28"
actions={
<>
<button
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
onClick={() => showUpdateLocalStorageDialog(systemStatus.localStoragePath)}
>
{t("common.edit")}
</button>
</>
}
/>
</div>
</div>
{storageList.map((storage) => ( {storageList.map((storage) => (
<div <div
key={storage.id} key={storage.id}

View File

@ -50,8 +50,8 @@ const UpdateLocalStorageDialog: React.FC<Props> = (props: Props) => {
</div> </div>
<div className="dialog-content-container max-w-xs"> <div className="dialog-content-container max-w-xs">
<p className="text-sm break-words mb-1">{t("setting.storage-section.update-local-path-description")}</p> <p className="text-sm break-words mb-1">{t("setting.storage-section.update-local-path-description")}</p>
<div className="flex flex-row"> <div className="flex flex-row items-center mb-2 gap-x-2">
<p className="text-sm text-gray-400 mb-2 break-all">e.g. {"assets/{filename}"}</p> <span className="text-sm text-gray-400 break-all">e.g. {"assets/{timestamp}_{filename}"}</span>
<LearnMore url="https://usememos.com/docs/local-storage" /> <LearnMore url="https://usememos.com/docs/local-storage" />
</div> </div>
<Input <Input

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "Dateiname", "file-name-placeholder": "Dateiname",
"type": "Typ", "type": "Typ",
"type-placeholder": "Dateityp" "type-placeholder": "Dateityp"
},
"download-link": {
"option": "Download-Link"
} }
} }
}, },

View File

@ -142,9 +142,6 @@
"file-name-placeholder": "File name", "file-name-placeholder": "File name",
"type": "Type", "type": "Type",
"type-placeholder": "File type" "type-placeholder": "File type"
},
"download-link": {
"option": "Download link"
} }
} }
}, },

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "फ़ाइल का नाम", "file-name-placeholder": "फ़ाइल का नाम",
"type": "प्रकार", "type": "प्रकार",
"type-placeholder": "फ़ाइल प्रकार" "type-placeholder": "फ़ाइल प्रकार"
},
"download-link": {
"option": "डाउनलोड लिंक"
} }
} }
}, },

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "Ime fajla", "file-name-placeholder": "Ime fajla",
"type": "Tip", "type": "Tip",
"type-placeholder": "Tip fajla" "type-placeholder": "Tip fajla"
},
"download-link": {
"option": "Link za preuzimanje"
} }
} }
}, },

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "Nome file", "file-name-placeholder": "Nome file",
"type": "Tipo", "type": "Tipo",
"type-placeholder": "Tipo file" "type-placeholder": "Tipo file"
},
"download-link": {
"option": "Link di download"
} }
} }
}, },

View File

@ -142,9 +142,6 @@
"file-name-placeholder": "ファイル名", "file-name-placeholder": "ファイル名",
"type": "タイプ", "type": "タイプ",
"type-placeholder": "ファイルタイプ" "type-placeholder": "ファイルタイプ"
},
"download-link": {
"option": "ダウンロードリンク"
} }
} }
}, },

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "파일 이름", "file-name-placeholder": "파일 이름",
"type": "종류", "type": "종류",
"type-placeholder": "파일 종류" "type-placeholder": "파일 종류"
},
"download-link": {
"option": "링크 다운로드"
} }
} }
}, },

View File

@ -133,9 +133,6 @@
"type": "Тип", "type": "Тип",
"type-placeholder": "Тип файла", "type-placeholder": "Тип файла",
"link-placeholder": "https://the.link.to/your/resource" "link-placeholder": "https://the.link.to/your/resource"
},
"download-link": {
"option": "Ссылка на скачивание"
} }
} }
}, },

View File

@ -255,9 +255,6 @@
"type": "类型", "type": "类型",
"type-placeholder": "文件类型" "type-placeholder": "文件类型"
}, },
"download-link": {
"option": "从外部链接下载"
},
"local-file": { "local-file": {
"choose": "选择文件...", "choose": "选择文件...",
"option": "本地文件" "option": "本地文件"

View File

@ -141,9 +141,6 @@
"file-name-placeholder": "檔案名稱", "file-name-placeholder": "檔案名稱",
"type": "類型", "type": "類型",
"type-placeholder": "檔案類型" "type-placeholder": "檔案類型"
},
"download-link": {
"option": "從外部連結下載"
} }
} }
}, },

View File

@ -133,16 +133,17 @@ const Auth = () => {
<div className="w-80 max-w-full h-full py-4 flex flex-col justify-start items-center"> <div className="w-80 max-w-full h-full py-4 flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center"> <div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="w-full flex flex-col justify-center items-center mb-2"> <div className="w-full flex flex-col justify-center items-center mb-2">
<img className="h-20 w-auto rounded-full shadow mr-1" src={systemStatus.customizedProfile.logoUrl} alt="" /> <img className="h-20 w-auto rounded-full shadow" src={systemStatus.customizedProfile.logoUrl} alt="" />
<p className="text-3xl text-black opacity-80 dark:text-gray-200">{systemStatus.customizedProfile.name}</p> <p className="mt-2 text-3xl text-black opacity-80 dark:text-gray-200">{systemStatus.customizedProfile.name}</p>
</div> </div>
{!disablePasswordLogin && ( {!disablePasswordLogin && (
<form className="w-full mt-4" onSubmit={handleFormSubmit}> <form className="w-full mt-2" onSubmit={handleFormSubmit}>
<div className="flex flex-col justify-start items-start w-full gap-4"> <div className="flex flex-col justify-start items-start w-full gap-4">
<Input <Input
className="w-full" className="w-full"
size="lg" size="lg"
type="text" type="text"
disabled={actionBtnLoadingState.isLoading}
placeholder={t("common.username")} placeholder={t("common.username")}
value={username} value={username}
onChange={handleUsernameInputChanged} onChange={handleUsernameInputChanged}
@ -152,6 +153,7 @@ const Auth = () => {
className="w-full" className="w-full"
size="lg" size="lg"
type="password" type="password"
disabled={actionBtnLoadingState.isLoading}
placeholder={t("common.password")} placeholder={t("common.password")}
value={password} value={password}
onChange={handlePasswordInputChanged} onChange={handlePasswordInputChanged}

View File

@ -91,7 +91,7 @@ const DailyReview = () => {
<div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300"> <div className="w-full flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-700 text-black dark:text-gray-300">
<div className="relative w-full flex flex-row justify-between items-center"> <div className="relative w-full flex flex-row justify-between items-center">
<p <p
className="px-2 py-1 flex flex-row justify-start items-center cursor-pointer select-none rounded hover:bg-gray-100 dark:hover:bg-zinc-700" className="px-2 py-1 flex flex-row justify-start items-center cursor-pointer select-none rounded opacity-80 hover:bg-gray-100 dark:hover:bg-zinc-700"
onClick={() => toggleShowDatePicker()} onClick={() => toggleShowDatePicker()}
> >
<Icon.Calendar className="w-5 h-auto mr-1" /> {t("daily-review.title")} <Icon.Calendar className="w-5 h-auto mr-1" /> {t("daily-review.title")}

View File

@ -96,7 +96,7 @@ const Explore = () => {
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />; return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />;
})} })}
{isComplete ? ( {isComplete ? (
memos.length === 0 && ( sortedMemos.length === 0 && (
<div className="w-full mt-16 mb-8 flex flex-col justify-center items-center italic"> <div className="w-full mt-16 mb-8 flex flex-col justify-center items-center italic">
<Empty /> <Empty />
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p> <p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>

View File

@ -1,20 +1,18 @@
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { Link, useLocation, useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Icon from "@/components/Icon"; import FloatingNavButton from "@/components/FloatingNavButton";
import Memo from "@/components/Memo"; import Memo from "@/components/Memo";
import UserAvatar from "@/components/UserAvatar";
import useLoading from "@/hooks/useLoading"; import useLoading from "@/hooks/useLoading";
import { useGlobalStore, useMemoStore } from "@/store/module"; import { useMemoStore, useUserStore } from "@/store/module";
import { useTranslate } from "@/utils/i18n";
const MemoDetail = () => { const MemoDetail = () => {
const t = useTranslate();
const params = useParams(); const params = useParams();
const location = useLocation();
const globalStore = useGlobalStore();
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const userStore = useUserStore();
const loadingState = useLoading(); const loadingState = useLoading();
const customizedProfile = globalStore.state.systemStatus.customizedProfile; const [user, setUser] = useState<User>();
const memoId = Number(params.memoId); const memoId = Number(params.memoId);
const memo = memoStore.state.memos.find((memo) => memo.id === memoId); const memo = memoStore.state.memos.find((memo) => memo.id === memoId);
@ -22,7 +20,9 @@ const MemoDetail = () => {
if (memoId && !isNaN(memoId)) { if (memoId && !isNaN(memoId)) {
memoStore memoStore
.fetchMemoById(memoId) .fetchMemoById(memoId)
.then(() => { .then(async (memo) => {
const user = await userStore.getUserByUsername(memo.creatorUsername);
setUser(user);
loadingState.setFinish(); loadingState.setFinish();
}) })
.catch((error) => { .catch((error) => {
@ -30,39 +30,35 @@ const MemoDetail = () => {
toast.error(error.response.data.message); toast.error(error.response.data.message);
}); });
} }
}, [location]); }, [memoId]);
return ( return (
<section className="relative top-0 w-full min-h-full overflow-x-hidden bg-zinc-100 dark:bg-zinc-800"> <>
<div className="relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-6"> <section className="relative top-0 w-full min-h-full overflow-x-hidden bg-zinc-100 dark:bg-zinc-800">
<div className="max-w-2xl w-full flex flex-row justify-center items-center px-4 py-2 mt-2 bg-zinc-100 dark:bg-zinc-800"> <div className="relative w-full min-h-full mx-auto flex flex-col justify-start items-center pb-6">
<div className="detail-header flex flex-row justify-start items-center"> <div className="w-full flex flex-col justify-start items-center py-8">
<img className="detail-logo h-10 w-auto rounded-lg mr-2" src={customizedProfile.logoUrl} alt="" /> <UserAvatar className="!w-20 h-auto mb-4 drop-shadow" avatarUrl={user?.avatarUrl} />
<p className="detail-name text-4xl tracking-wide text-black dark:text-white">{customizedProfile.name}</p> <div>
<p className="text-2xl font-bold text-gray-700 dark:text-gray-300">{user?.nickname}</p>
</div>
</div> </div>
{!loadingState.isLoading &&
(memo ? (
<>
<main className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4">
<Memo memo={memo} />
</main>
</>
) : (
<>
<p>Not found</p>
</>
))}
</div> </div>
{!loadingState.isLoading && </section>
(memo ? (
<> <FloatingNavButton />
<main className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4"> </>
<Memo memo={memo} />
</main>
<div className="mt-4 w-full flex flex-row justify-center items-center gap-2">
<Link
to="/"
className="flex flex-row justify-center items-center text-gray-600 dark:text-gray-300 text-sm px-3 hover:opacity-80 hover:underline"
>
<Icon.Home className="w-4 h-auto mr-1 -mt-0.5" /> {t("router.back-to-home")}
</Link>
</div>
</>
) : (
<>
<p>Not found</p>
</>
))}
</div>
</section>
); );
}; };

View File

@ -39,7 +39,7 @@ const UserProfile = () => {
<div className="w-full flex flex-row justify-start items-start"> <div className="w-full flex flex-row justify-start items-start">
<div className="flex-grow shrink w-full"> <div className="flex-grow shrink w-full">
<div className="w-full flex flex-col justify-start items-center py-8"> <div className="w-full flex flex-col justify-start items-center py-8">
<UserAvatar className="w-16 h-auto mb-4 drop-shadow" avatarUrl={user?.avatarUrl} /> <UserAvatar className="!w-20 h-auto mb-4 drop-shadow" avatarUrl={user?.avatarUrl} />
<div> <div>
<p className="text-2xl font-bold text-gray-700 dark:text-gray-300">{user?.nickname}</p> <p className="text-2xl font-bold text-gray-700 dark:text-gray-300">{user?.nickname}</p>
</div> </div>

View File

@ -218,7 +218,7 @@ const router = createBrowserRouter([
}, },
}, },
{ {
path: "u/:username", path: "/u/:username",
element: <UserProfile />, element: <UserProfile />,
loader: async () => { loader: async () => {
await initialGlobalStateLoader(); await initialGlobalStateLoader();

View File

@ -18,7 +18,6 @@ interface ResourceCreate {
filename: string; filename: string;
externalLink: string; externalLink: string;
type: string; type: string;
downloadToLocal: boolean;
} }
interface ResourcePatch { interface ResourcePatch {