mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
@ -169,6 +169,24 @@ func (s *APIV1Service) SignInSSO(c echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Incorrect login credentials, please try again")
|
||||
}
|
||||
if user == nil {
|
||||
allowSignUpSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{
|
||||
Name: SystemSettingAllowSignUpName.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting").SetInternal(err)
|
||||
}
|
||||
|
||||
allowSignUpSettingValue := false
|
||||
if allowSignUpSetting != nil {
|
||||
err = json.Unmarshal([]byte(allowSignUpSetting.Value), &allowSignUpSettingValue)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting allow signup").SetInternal(err)
|
||||
}
|
||||
}
|
||||
if !allowSignUpSettingValue {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "signup is disabled").SetInternal(err)
|
||||
}
|
||||
|
||||
userCreate := &store.User{
|
||||
Username: userInfo.Identifier,
|
||||
// The new signup user should be normal user by default.
|
||||
|
@ -656,7 +656,7 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
|
||||
return fmt.Errorf("Failed to find SystemSettingStorageServiceIDName: %s", err)
|
||||
}
|
||||
|
||||
storageServiceID := DatabaseStorage
|
||||
storageServiceID := LocalStorage
|
||||
if systemSettingStorageServiceID != nil {
|
||||
err = json.Unmarshal([]byte(systemSettingStorageServiceID.Value), &storageServiceID)
|
||||
if err != nil {
|
||||
@ -672,15 +672,13 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc
|
||||
}
|
||||
create.Blob = fileBytes
|
||||
return nil
|
||||
}
|
||||
|
||||
} else if storageServiceID == LocalStorage {
|
||||
// `LocalStorage` means save blob into local disk
|
||||
if storageServiceID == LocalStorage {
|
||||
systemSettingLocalStoragePath, err := s.GetSystemSetting(ctx, &store.FindSystemSetting{Name: SystemSettingLocalStoragePathName.String()})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to find SystemSettingLocalStoragePathName: %s", err)
|
||||
}
|
||||
localStoragePath := "assets/{filename}"
|
||||
localStoragePath := "assets/{timestamp}_{filename}"
|
||||
if systemSettingLocalStoragePath != nil && systemSettingLocalStoragePath.Value != "" {
|
||||
err = json.Unmarshal([]byte(systemSettingLocalStoragePath.Value), &localStoragePath)
|
||||
if err != nil {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
const (
|
||||
// LocalStorage means the storage service is local file system.
|
||||
// Default storage service is local file system.
|
||||
LocalStorage int32 = -1
|
||||
// DatabaseStorage means the storage service is database.
|
||||
DatabaseStorage int32 = 0
|
||||
@ -214,7 +215,7 @@ func (s *APIV1Service) DeleteStorage(c echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find storage").SetInternal(err)
|
||||
}
|
||||
if systemSetting != nil {
|
||||
storageServiceID := DatabaseStorage
|
||||
storageServiceID := LocalStorage
|
||||
err = json.Unmarshal([]byte(systemSetting.Value), &storageServiceID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
|
||||
|
@ -89,7 +89,7 @@ func (s *APIV1Service) GetSystemStatus(c echo.Context) error {
|
||||
Appearance: "system",
|
||||
ExternalURL: "",
|
||||
},
|
||||
StorageServiceID: DatabaseStorage,
|
||||
StorageServiceID: LocalStorage,
|
||||
LocalStoragePath: "assets/{timestamp}_{filename}",
|
||||
MemoDisplayWithUpdatedTs: false,
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ INSERT INTO
|
||||
memo (`id`, `content`, `creator_id`)
|
||||
VALUES
|
||||
(
|
||||
1001,
|
||||
1,
|
||||
"#Hello 👋 Welcome to memos.",
|
||||
101
|
||||
);
|
||||
@ -16,7 +16,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1002,
|
||||
2,
|
||||
'#TODO
|
||||
- [x] Take more photos about **🌄 sunset**;
|
||||
- [x] Clean the room;
|
||||
@ -35,7 +35,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1003,
|
||||
3,
|
||||
"**[Slash](https://github.com/boojack/slash)**: A bookmarking and url shortener, save and share your links very easily.
|
||||

|
||||
|
||||
@ -54,7 +54,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1004,
|
||||
4,
|
||||
'#TODO
|
||||
- [x] Take more photos about **🌄 sunset**;
|
||||
- [ ] Clean the classroom;
|
||||
@ -74,7 +74,7 @@ INSERT INTO
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
1005,
|
||||
5,
|
||||
'三人行,必有我师焉!👨🏫',
|
||||
102,
|
||||
'PUBLIC'
|
||||
|
@ -1,9 +1,9 @@
|
||||
INSERT INTO
|
||||
memo_organizer (`memo_id`, `user_id`, `pinned`)
|
||||
VALUES
|
||||
(1001, 101, 1);
|
||||
(1, 101, 1);
|
||||
|
||||
INSERT INTO
|
||||
memo_organizer (`memo_id`, `user_id`, `pinned`)
|
||||
VALUES
|
||||
(1003, 101, 1);
|
||||
(3, 101, 1);
|
27
web/src/components/FloatingNavButton.tsx
Normal file
27
web/src/components/FloatingNavButton.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { Dropdown, IconButton, Menu, MenuButton, MenuItem } from "@mui/joy";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Icon from "./Icon";
|
||||
|
||||
const FloatingNavButton = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown>
|
||||
<div className="fixed bottom-6 right-6">
|
||||
<MenuButton
|
||||
slots={{ root: IconButton }}
|
||||
slotProps={{ root: { className: "!bg-white dark:!bg-zinc-900 drop-shadow", variant: "outlined", color: "neutral" } }}
|
||||
>
|
||||
<Icon.MoreVertical className="w-5 h-auto" />
|
||||
</MenuButton>
|
||||
</div>
|
||||
<Menu placement="top-end">
|
||||
<MenuItem onClick={() => navigate("/")}>Back to home</MenuItem>
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FloatingNavButton;
|
@ -1,5 +1,4 @@
|
||||
import { Divider } from "@mui/joy";
|
||||
import { isEqual, uniqWith } from "lodash-es";
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@ -7,7 +6,7 @@ import { Link } from "react-router-dom";
|
||||
import { UNKNOWN_ID } from "@/helpers/consts";
|
||||
import { getRelativeTimeString } from "@/helpers/datetime";
|
||||
import { useFilterStore, useMemoStore, useUserStore } from "@/store/module";
|
||||
import { useMemoCacheStore, useUserV1Store } from "@/store/v1";
|
||||
import { useUserV1Store } from "@/store/v1";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||
import { showCommonDialog } from "./Dialog/CommonDialog";
|
||||
@ -23,24 +22,20 @@ import "@/less/memo.less";
|
||||
|
||||
interface Props {
|
||||
memo: Memo;
|
||||
showCreator?: boolean;
|
||||
showVisibility?: boolean;
|
||||
showRelatedMemos?: boolean;
|
||||
lazyRendering?: boolean;
|
||||
}
|
||||
|
||||
const Memo: React.FC<Props> = (props: Props) => {
|
||||
const { memo, showCreator, showRelatedMemos, lazyRendering } = props;
|
||||
const { memo, lazyRendering } = props;
|
||||
const { i18n } = useTranslation();
|
||||
const t = useTranslate();
|
||||
const filterStore = useFilterStore();
|
||||
const userStore = useUserStore();
|
||||
const memoStore = useMemoStore();
|
||||
const memoCacheStore = useMemoCacheStore();
|
||||
const userV1Store = useUserV1Store();
|
||||
const [shouldRender, setShouldRender] = useState<boolean>(lazyRendering ? false : true);
|
||||
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.displayTs));
|
||||
const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]);
|
||||
const [displayTime, setDisplayTime] = useState<string>(getRelativeTimeString(memo.displayTs));
|
||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||
const readonly = userStore.isVisitorMode() || userStore.getCurrentUsername() !== memo.creatorUsername;
|
||||
const creator = userV1Store.getUserByUsername(memo.creatorUsername);
|
||||
@ -50,27 +45,12 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
userV1Store.getOrFetchUserByUsername(memo.creatorUsername);
|
||||
}, [memo.creatorUsername]);
|
||||
|
||||
// Prepare related memos.
|
||||
useEffect(() => {
|
||||
Promise.allSettled(memo.relationList.map((memoRelation) => memoCacheStore.getOrFetchMemoById(memoRelation.relatedMemoId))).then(
|
||||
(results) => {
|
||||
const memoList = [];
|
||||
for (const result of results) {
|
||||
if (result.status === "fulfilled") {
|
||||
memoList.push(result.value);
|
||||
}
|
||||
}
|
||||
setRelatedMemoList(uniqWith(memoList, isEqual));
|
||||
}
|
||||
);
|
||||
}, [memo.relationList]);
|
||||
|
||||
// Update display time string.
|
||||
useEffect(() => {
|
||||
let intervalFlag: any = -1;
|
||||
if (Date.now() - memo.displayTs < 1000 * 60 * 60 * 24) {
|
||||
intervalFlag = setInterval(() => {
|
||||
setCreatedTimeStr(getRelativeTimeString(memo.displayTs));
|
||||
setDisplayTime(getRelativeTimeString(memo.displayTs));
|
||||
}, 1000 * 1);
|
||||
}
|
||||
|
||||
@ -246,26 +226,25 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
<>
|
||||
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}>
|
||||
<div className="memo-top-wrapper">
|
||||
<div className="status-text-container">
|
||||
{showCreator && creator && (
|
||||
<p className="w-full max-w-[calc(100%-20px)] flex flex-row justify-start items-center mr-1">
|
||||
{creator && (
|
||||
<>
|
||||
<Link className="flex flex-row justify-start items-center" to={`/u/${memo.creatorUsername}`}>
|
||||
<UserAvatar className="!w-5 !h-auto mr-1" avatarUrl={creator.avatarUrl} />
|
||||
<span className="text-sm text-gray-600 dark:text-zinc-300">{creator.nickname}</span>
|
||||
<span className="text-sm text-gray-600 max-w-[8em] truncate dark:text-zinc-300">{creator.nickname}</span>
|
||||
</Link>
|
||||
<Icon.Dot className="w-4 h-auto text-gray-400 dark:text-zinc-400" />
|
||||
</>
|
||||
)}
|
||||
<Link className="time-text" to={`/m/${memo.id}`} onClick={handleMemoCreatedTimeClick}>
|
||||
{createdTimeStr}
|
||||
</Link>
|
||||
</div>
|
||||
<span className="text-sm text-gray-400" onClick={handleMemoCreatedTimeClick}>
|
||||
{displayTime}
|
||||
</span>
|
||||
</p>
|
||||
<div className="btns-container space-x-2">
|
||||
{memo.pinned && <Icon.Bookmark className="w-4 h-auto rounded text-green-600" />}
|
||||
{!readonly && (
|
||||
<>
|
||||
<span className="btn more-action-btn">
|
||||
<Icon.MoreHorizontal className="icon-img" />
|
||||
<Icon.MoreVertical className="icon-img" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container min-w-[6em]">
|
||||
@ -306,24 +285,8 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
onMemoContentDoubleClick={handleMemoContentDoubleClick}
|
||||
/>
|
||||
<MemoResourceListView resourceList={memo.resourceList} />
|
||||
{!showRelatedMemos && <MemoRelationListView relationList={memo.relationList} />}
|
||||
<MemoRelationListView relationList={memo.relationList} />
|
||||
</div>
|
||||
|
||||
{showRelatedMemos && relatedMemoList.length > 0 && (
|
||||
<>
|
||||
<p className="text-sm dark:text-gray-300 my-2 pl-4 opacity-50 flex flex-row items-center">
|
||||
<Icon.Link className="w-4 h-auto mr-1" />
|
||||
<span>Related memos</span>
|
||||
</p>
|
||||
{relatedMemoList.map((relatedMemo) => {
|
||||
return (
|
||||
<div key={relatedMemo.id} className="w-full">
|
||||
<Memo memo={relatedMemo} showCreator />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -9,12 +9,7 @@ import Empty from "./Empty";
|
||||
import Memo from "./Memo";
|
||||
import "@/less/memo-list.less";
|
||||
|
||||
interface Props {
|
||||
showCreator?: boolean;
|
||||
}
|
||||
|
||||
const MemoList: React.FC<Props> = (props: Props) => {
|
||||
const { showCreator } = props;
|
||||
const MemoList: React.FC = () => {
|
||||
const t = useTranslate();
|
||||
const memoStore = useMemoStore();
|
||||
const userStore = useUserStore();
|
||||
@ -142,7 +137,7 @@ const MemoList: React.FC<Props> = (props: Props) => {
|
||||
return (
|
||||
<div className="memo-list-container">
|
||||
{sortedMemos.map((memo) => (
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} lazyRendering showVisibility showCreator={showCreator} />
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} lazyRendering showVisibility />
|
||||
))}
|
||||
{isFetching ? (
|
||||
<div className="status-text-container fetching-tip">
|
||||
|
@ -156,7 +156,10 @@ const PreferencesSection = () => {
|
||||
{userList.map((user) => (
|
||||
<tr key={user.id}>
|
||||
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900">{user.id}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.username}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">
|
||||
{user.username}
|
||||
<span className="ml-1 italic">{user.rowStatus === "ARCHIVED" && "(Archived)"}</span>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.nickname}</td>
|
||||
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500">{user.email}</td>
|
||||
<td className="relative whitespace-nowrap py-2 pl-3 pr-4 text-right text-sm font-medium flex justify-end">
|
||||
|
@ -8,7 +8,7 @@ interface Props {
|
||||
const UserAvatar = (props: Props) => {
|
||||
const { avatarUrl, className } = props;
|
||||
return (
|
||||
<div className={classNames(`w-8 h-8 overflow-clip`, className)}>
|
||||
<div className={classNames(`w-8 h-auto overflow-clip rounded-full`, className)}>
|
||||
<img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.webp"} alt="" />
|
||||
</div>
|
||||
);
|
||||
|
@ -130,29 +130,22 @@ export const getRelativeTimeString = (time: number, locale = i18n.language, form
|
||||
|
||||
// numeric: "auto" provides "yesterday" for 1 day ago, "always" provides "1 day ago"
|
||||
const formatOpts = { style: formatStyle, numeric: "auto" } as Intl.RelativeTimeFormatOptions;
|
||||
|
||||
const relTime = new Intl.RelativeTimeFormat(locale, formatOpts);
|
||||
|
||||
if (pastTimeMillis < minMillis) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / secMillis), "second");
|
||||
}
|
||||
|
||||
if (pastTimeMillis < hourMillis) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / minMillis), "minute");
|
||||
}
|
||||
|
||||
if (pastTimeMillis < dayMillis) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / hourMillis), "hour");
|
||||
}
|
||||
|
||||
if (pastTimeMillis < dayMillis * 7) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / dayMillis), "day");
|
||||
}
|
||||
|
||||
if (pastTimeMillis < dayMillis * 30) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / (dayMillis * 7)), "week");
|
||||
}
|
||||
|
||||
if (pastTimeMillis < dayMillis * 365) {
|
||||
return relTime.format(-Math.round(pastTimeMillis / (dayMillis * 30)), "month");
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
.memo-content-wrapper {
|
||||
@apply w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-200;
|
||||
@apply w-full flex flex-col justify-start items-start text-gray-800 dark:text-gray-300;
|
||||
|
||||
> .memo-content-text {
|
||||
@apply w-full max-w-full word-break text-base leading-6;
|
||||
|
@ -8,30 +8,6 @@
|
||||
> .memo-top-wrapper {
|
||||
@apply flex flex-row justify-between items-center w-full h-6 mb-1;
|
||||
|
||||
> .status-text-container {
|
||||
@apply flex flex-row justify-start items-center;
|
||||
|
||||
> .time-text {
|
||||
@apply text-sm text-gray-400;
|
||||
}
|
||||
|
||||
> .name-text {
|
||||
@apply ml-1 text-sm text-gray-400 cursor-pointer hover:opacity-80;
|
||||
}
|
||||
|
||||
> .status-text {
|
||||
@apply text-xs cursor-pointer ml-2 rounded border px-1;
|
||||
|
||||
&.public {
|
||||
@apply border-green-600 text-green-600;
|
||||
}
|
||||
|
||||
&.protected {
|
||||
@apply border-gray-400 text-gray-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .btns-container {
|
||||
@apply flex flex-row justify-end items-center relative shrink-0;
|
||||
|
||||
@ -56,7 +32,7 @@
|
||||
@apply flex flex-row justify-center items-center leading-6 text-sm rounded hover:bg-gray-200 dark:hover:bg-zinc-600;
|
||||
|
||||
&.more-action-btn {
|
||||
@apply w-auto opacity-60 cursor-default hover:bg-transparent;
|
||||
@apply w-auto opacity-50 cursor-default hover:bg-transparent;
|
||||
|
||||
> .icon-img {
|
||||
@apply w-4 h-auto dark:text-gray-300;
|
||||
|
@ -93,7 +93,7 @@ const Explore = () => {
|
||||
<main className="relative w-full h-auto flex flex-col justify-start items-start">
|
||||
<MemoFilter />
|
||||
{sortedMemos.map((memo) => {
|
||||
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} showCreator />;
|
||||
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />;
|
||||
})}
|
||||
{isComplete ? (
|
||||
memos.length === 0 && (
|
||||
|
@ -33,7 +33,7 @@ const MemoDetail = () => {
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
<section className="relative top-0 w-full h-full overflow-y-auto overflow-x-hidden bg-zinc-100 dark:bg-zinc-800">
|
||||
<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">
|
||||
<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="detail-header flex flex-row justify-start items-center">
|
||||
@ -45,7 +45,7 @@ const MemoDetail = () => {
|
||||
(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} showCreator showRelatedMemos />
|
||||
<Memo memo={memo} />
|
||||
</main>
|
||||
<div className="mt-4 w-full flex flex-row justify-center items-center gap-2">
|
||||
<Link
|
||||
|
74
web/src/pages/UserProfile.tsx
Normal file
74
web/src/pages/UserProfile.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { useEffect } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import FloatingNavButton from "@/components/FloatingNavButton";
|
||||
import MemoFilter from "@/components/MemoFilter";
|
||||
import MemoList from "@/components/MemoList";
|
||||
import UserAvatar from "@/components/UserAvatar";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useGlobalStore, useUserStore } from "@/store/module";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
const UserProfile = () => {
|
||||
const t = useTranslate();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const loadingState = useLoading();
|
||||
const user = userStore.state.user;
|
||||
|
||||
useEffect(() => {
|
||||
const currentUsername = userStore.getCurrentUsername();
|
||||
userStore
|
||||
.getUserByUsername(currentUsername)
|
||||
.then(() => {
|
||||
loadingState.setFinish();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
toast.error(t("message.user-not-found"));
|
||||
});
|
||||
}, [userStore.getCurrentUsername()]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.setting.locale) {
|
||||
globalStore.setLocale(user.setting.locale);
|
||||
}
|
||||
}, [user?.setting.locale]);
|
||||
|
||||
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">
|
||||
{!loadingState.isLoading &&
|
||||
(user ? (
|
||||
<>
|
||||
<main className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4">
|
||||
<div className="w-full flex flex-row justify-start items-start">
|
||||
<div className="flex-grow shrink w-full">
|
||||
<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} />
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-gray-700 dark:text-gray-300">{user?.nickname}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg">
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p>Not found</p>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<FloatingNavButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfile;
|
@ -13,6 +13,7 @@ const Auth = lazy(() => import("@/pages/Auth"));
|
||||
const AuthCallback = lazy(() => import("@/pages/AuthCallback"));
|
||||
const Explore = lazy(() => import("@/pages/Explore"));
|
||||
const Home = lazy(() => import("@/pages/Home"));
|
||||
const UserProfile = lazy(() => import("@/pages/UserProfile"));
|
||||
const MemoDetail = lazy(() => import("@/pages/MemoDetail"));
|
||||
const EmbedMemo = lazy(() => import("@/pages/EmbedMemo"));
|
||||
const NotFound = lazy(() => import("@/pages/NotFound"));
|
||||
@ -78,28 +79,6 @@ const router = createBrowserRouter([
|
||||
return redirect("/explore");
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "u/:username",
|
||||
element: <Home />,
|
||||
loader: async () => {
|
||||
await initialGlobalStateLoader();
|
||||
|
||||
try {
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
|
||||
const { user } = store.getState().user;
|
||||
const { systemStatus } = store.getState().global;
|
||||
|
||||
if (isNullorUndefined(user) && systemStatus.disablePublicMemos) {
|
||||
return redirect("/auth");
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "explore",
|
||||
element: <Explore />,
|
||||
@ -238,6 +217,20 @@ const router = createBrowserRouter([
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "u/:username",
|
||||
element: <UserProfile />,
|
||||
loader: async () => {
|
||||
await initialGlobalStateLoader();
|
||||
|
||||
try {
|
||||
await initialUserState();
|
||||
} catch (error) {
|
||||
// do nth
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
element: <NotFound />,
|
||||
|
Reference in New Issue
Block a user