Merge branch 'main' into feat/sharing

This commit is contained in:
Philipinho
2025-04-09 20:22:44 +01:00
10 changed files with 76 additions and 32 deletions

View File

@ -1,7 +1,7 @@
{
"name": "client",
"private": true,
"version": "0.9.0",
"version": "0.10.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",

View File

@ -209,6 +209,7 @@ export default function PageEditor({
queryClient.setQueryData(["pages", slugId], {
...pageData,
content: newContent,
updatedAt: new Date(),
});
}
}, 3000);

View File

@ -17,6 +17,13 @@ import {
import { modals } from "@mantine/modals";
import { notifications } from "@mantine/notifications";
import { useTranslation } from "react-i18next";
import { useSpaceAbility } from "@/features/space/permissions/use-space-ability.ts";
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
import { useParams } from "react-router-dom";
import {
SpaceCaslAction,
SpaceCaslSubject,
} from "@/features/space/permissions/permissions.type.ts";
interface Props {
pageId: string;
@ -36,6 +43,11 @@ function HistoryList({ pageId }: Props) {
const [mainEditorTitle] = useAtom(titleEditorAtom);
const [, setHistoryModalOpen] = useAtom(historyAtoms);
const { spaceSlug } = useParams();
const { data: space } = useSpaceQuery(spaceSlug);
const spaceRules = space?.membership?.permissions;
const spaceAbility = useSpaceAbility(spaceRules);
const confirmModal = () =>
modals.openConfirmModal({
title: t("Please confirm your action"),
@ -103,20 +115,26 @@ function HistoryList({ pageId }: Props) {
))}
</ScrollArea>
<Divider />
<Group p="xs" wrap="nowrap">
<Button size="compact-md" onClick={confirmModal}>
{t("Restore")}
</Button>
<Button
variant="default"
size="compact-md"
onClick={() => setHistoryModalOpen(false)}
>
{t("Cancel")}
</Button>
</Group>
{spaceAbility.cannot(
SpaceCaslAction.Manage,
SpaceCaslSubject.Page,
) ? null : (
<>
<Divider />
<Group p="xs" wrap="nowrap">
<Button size="compact-md" onClick={confirmModal}>
{t("Restore")}
</Button>
<Button
variant="default"
size="compact-md"
onClick={() => setHistoryModalOpen(false)}
>
{t("Cancel")}
</Button>
</Group>
</>
)}
</div>
);
}

View File

@ -12,7 +12,7 @@ import {
IconTrash,
IconWifiOff,
} from "@tabler/icons-react";
import React from "react";
import React, { useEffect } from "react";
import useToggleAside from "@/hooks/use-toggle-aside.tsx";
import { useAtom } from "jotai";
import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts";
@ -34,6 +34,7 @@ import {
} from "@/features/editor/atoms/editor-atoms.ts";
import { formattedDate, timeAgo } from "@/lib/time.ts";
import MovePageModal from "@/features/page/components/move-page-modal.tsx";
import { useTimeAgo } from "@/hooks/use-time-ago.tsx";
interface PageHeaderMenuProps {
readOnly?: boolean;
@ -102,6 +103,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
{ open: openMovePageModal, close: closeMoveSpaceModal },
] = useDisclosure(false);
const [pageEditor] = useAtom(pageEditorAtom);
const pageUpdatedAt = useTimeAgo(page.updatedAt);
const handleCopyLink = () => {
const pageUrl =
@ -208,7 +210,7 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) {
<Tooltip
label={t("Edited by {{name}} {{time}}", {
name: page.lastUpdatedBy.name,
time: timeAgo(page.updatedAt),
time: pageUpdatedAt,
})}
position="left-start"
>

View File

@ -0,0 +1,16 @@
import { timeAgo } from "@/lib/time.ts";
import { useEffect, useState } from "react";
export function useTimeAgo(date: Date | string) {
const [value, setValue] = useState(() => timeAgo(new Date(date)));
useEffect(() => {
const interval = setInterval(() => {
setValue(timeAgo(new Date(date)));
}, 5 * 1000);
return () => clearInterval(interval);
}, [date]);
return value;
}

View File

@ -1,6 +1,6 @@
{
"name": "server",
"version": "0.9.0",
"version": "0.10.0",
"description": "",
"author": "",
"private": true,

View File

@ -47,13 +47,18 @@ export class AuthService {
includePassword: true,
});
const errorMessage = 'email or password does not match';
if (!user || user?.deletedAt) {
throw new UnauthorizedException(errorMessage);
}
const isPasswordMatch = await comparePasswordHash(
loginDto.password,
user?.password,
user.password,
);
if (!user || !isPasswordMatch || user.deletedAt) {
throw new UnauthorizedException('email or password does not match');
if (!isPasswordMatch) {
throw new UnauthorizedException(errorMessage);
}
user.lastLoginAt = new Date();