switch to nx monorepo

This commit is contained in:
Philipinho
2024-01-09 18:58:26 +01:00
parent e1bb2632b8
commit 093e634c0b
273 changed files with 11419 additions and 31 deletions

View File

@ -0,0 +1,4 @@
import { atom } from "jotai";
export const historyAtoms = atom<boolean>(false);
export const activeHistoryIdAtom = atom<string>('');

View File

@ -0,0 +1,33 @@
import '@/features/editor/styles/index.css';
import React, { useEffect } from 'react';
import { EditorContent, useEditor } from '@tiptap/react';
import { mainExtensions } from '@/features/editor/extensions/extensions';
import { Title } from '@mantine/core';
export interface HistoryEditorProps {
title: string;
content: any;
}
export function HistoryEditor({ title, content }: HistoryEditorProps) {
const editor = useEditor({
extensions: mainExtensions,
editable: false,
});
useEffect(() => {
if (editor && content) {
editor.commands.setContent(content);
}
}, [title, content, editor]);
return (
<>
<div>
<Title order={1}>{title}</Title>
{editor && <EditorContent editor={editor} />}
</div>
</>
);
}

View File

@ -0,0 +1,40 @@
import { Text, Group, UnstyledButton } from '@mantine/core';
import { UserAvatar } from '@/components/ui/user-avatar';
import { formatDate } from '@/lib/time';
import classes from './history.module.css';
import clsx from 'clsx';
interface HistoryItemProps {
historyItem: any,
onSelect: (id: string) => void;
isActive: boolean;
}
function HistoryItem({ historyItem, onSelect, isActive }: HistoryItemProps) {
return (
<UnstyledButton p="xs" onClick={() => onSelect(historyItem.id)}
className={clsx(classes.history, { [classes.active]: isActive })}
>
<Group wrap="nowrap">
<div>
<Text size="sm">
{formatDate(new Date(historyItem.createdAt))}
</Text>
<div style={{ flex: 1 }}>
<Group gap={4} wrap="nowrap">
<UserAvatar color="blue" size="sm" avatarUrl={historyItem.lastUpdatedBy.avatarUrl}
name={historyItem.lastUpdatedBy.name} />
<Text size="sm" c="dimmed" lineClamp={1}>
{historyItem.lastUpdatedBy.name}
</Text>
</Group>
</div>
</div>
</Group>
</UnstyledButton>
);
}
export default HistoryItem;

View File

@ -0,0 +1,85 @@
import { usePageHistoryListQuery, usePageHistoryQuery } from '@/features/page-history/queries/page-history-query';
import { useParams } from 'react-router-dom';
import HistoryItem from '@/features/page-history/components/history-item';
import { activeHistoryIdAtom, historyAtoms } from '@/features/page-history/atoms/history-atoms';
import { useAtom } from 'jotai';
import { useCallback, useEffect } from 'react';
import { Button, ScrollArea, Group, Divider, Text } from '@mantine/core';
import { pageEditorAtom, titleEditorAtom } from '@/features/editor/atoms/editor-atoms';
import { modals } from '@mantine/modals';
import { notifications } from '@mantine/notifications';
function HistoryList() {
const [activeHistoryId, setActiveHistoryId] = useAtom(activeHistoryIdAtom);
const { pageId } = useParams();
const { data, isLoading, isError } = usePageHistoryListQuery(pageId);
const { data: activeHistoryData } = usePageHistoryQuery(activeHistoryId);
const [mainEditor] = useAtom(pageEditorAtom);
const [mainEditorTitle] = useAtom(titleEditorAtom);
const [, setHistoryModalOpen] = useAtom(historyAtoms);
const confirmModal = () => modals.openConfirmModal({
title: 'Please confirm your action',
children: (
<Text size="sm">
Are you sure you want to restore this version? Any changes not versioned will be lost.
</Text>
),
labels: { confirm: 'Confirm', cancel: 'Cancel' },
onConfirm: handleRestore,
});
const handleRestore = useCallback(() => {
if (activeHistoryData) {
mainEditorTitle.chain().clearContent().setContent(activeHistoryData.title, true).run();
mainEditor.chain().clearContent().setContent(activeHistoryData.content).run();
setHistoryModalOpen(false);
notifications.show({ message: 'Successfully restored' });
}
}, [activeHistoryData]);
useEffect(() => {
if (data && data.length > 0 && !activeHistoryId) {
setActiveHistoryId(data[0].id);
}
}, [data]);
if (isLoading) {
return <></>;
}
if (isError) {
return <div>Error loading page history.</div>;
}
if (!data || data.length === 0) {
return <>No page history saved yet.</>;
}
return (
<div>
<ScrollArea h={620} w="100%" type="scroll" scrollbarSize={5}>
{data && data.map((historyItem, index) => (
<HistoryItem
key={index}
historyItem={historyItem}
onSelect={setActiveHistoryId}
isActive={historyItem.id === activeHistoryId}
/>
))}
</ScrollArea>
<Divider />
<Group p="xs" wrap="nowrap">
<Button size="compact-md" onClick={confirmModal}>Restore</Button>
<Button variant="default" size="compact-md" onClick={() => setHistoryModalOpen(false)}>Cancel</Button>
</Group>
</div>
);
}
export default HistoryList;

View File

@ -0,0 +1,27 @@
import { ScrollArea } from '@mantine/core';
import HistoryList from '@/features/page-history/components/history-list';
import classes from './history.module.css';
import { useAtom } from 'jotai';
import { activeHistoryIdAtom } from '@/features/page-history/atoms/history-atoms';
import HistoryView from '@/features/page-history/components/history-view';
export default function HistoryModalBody() {
const [activeHistoryId] = useAtom(activeHistoryIdAtom);
return (
<div className={classes.sidebarFlex}>
<nav className={classes.sidebar}>
<div className={classes.sidebarMain}>
<HistoryList />
</div>
</nav>
<ScrollArea h="650" w="100%" scrollbarSize={5}>
<div className={classes.sidebarRightSection}>
{activeHistoryId && <HistoryView historyId={activeHistoryId} />}
</div>
</ScrollArea>
</div>
);
}

View File

@ -0,0 +1,27 @@
import { Modal, Text } from '@mantine/core';
import { useAtom } from 'jotai';
import { historyAtoms } from '@/features/page-history/atoms/history-atoms';
import HistoryModalBody from '@/features/page-history/components/history-modal-body';
export default function HistoryModal() {
const [isModalOpen, setModalOpen] = useAtom(historyAtoms);
return (
<>
<Modal.Root size={1200} opened={isModalOpen} onClose={() => setModalOpen(false)}>
<Modal.Overlay />
<Modal.Content style={{ overflow: 'hidden' }}>
<Modal.Header>
<Modal.Title>
<Text size="md" fw={500}>Page history</Text>
</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body>
<HistoryModalBody />
</Modal.Body>
</Modal.Content>
</Modal.Root>
</>
);
}

View File

@ -0,0 +1,26 @@
import { usePageHistoryQuery } from '@/features/page-history/queries/page-history-query';
import { HistoryEditor } from '@/features/page-history/components/history-editor';
interface HistoryProps {
historyId: string;
}
function HistoryView({ historyId }: HistoryProps) {
const { data, isLoading, isError } = usePageHistoryQuery(historyId);
if (isLoading) {
return <></>;
}
if (isError || !data) {
return <div>Error fetching page data.</div>;
}
return (data &&
<div>
<HistoryEditor content={data.content} title={data.title} />
</div>
);
}
export default HistoryView;

View File

@ -0,0 +1,37 @@
.history {
display: block;
width: 100%;
padding: var(--mantine-spacing-md);
color: light-dark(var(--mantine-color-black), var(--mantine-color-dark-0));
@mixin hover {
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8));
}
}
.active {
background-color: light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-8));
}
.sidebar {
max-height: rem(700px);
width: rem(250px);
padding: var(--mantine-spacing-sm);
display: flex;
flex-direction: column;
border-right: rem(1px) solid
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
.sidebarFlex {
display: flex;
}
.sidebarMain {
flex: 1;
}
.sidebarRightSection {
flex: 1;
padding: rem(16px) rem(40px);
}

View File

@ -0,0 +1,20 @@
import { useQuery, UseQueryResult } from '@tanstack/react-query';
import { getPageHistoryById, getPageHistoryList } from '@/features/page-history/services/page-history-service';
import { IPageHistory } from '@/features/page-history/types/page.types';
export function usePageHistoryListQuery(pageId: string): UseQueryResult<IPageHistory[], Error> {
return useQuery({
queryKey: ['page-history-list', pageId],
queryFn: () => getPageHistoryList(pageId),
enabled: !!pageId,
});
}
export function usePageHistoryQuery(historyId: string): UseQueryResult<IPageHistory, Error> {
return useQuery({
queryKey: ['page-history', historyId],
queryFn: () => getPageHistoryById(historyId),
enabled: !!historyId,
staleTime: 10 * 60 * 1000,
});
}

View File

@ -0,0 +1,12 @@
import api from '@/lib/api-client';
import { IPageHistory } from '@/features/page-history/types/page.types';
export async function getPageHistoryList(pageId: string): Promise<IPageHistory[]> {
const req = await api.post<IPageHistory[]>('/pages/history', { pageId });
return req.data as IPageHistory[];
}
export async function getPageHistoryById(id: string): Promise<IPageHistory> {
const req = await api.post<IPageHistory>('/pages/history/details', { id });
return req.data as IPageHistory;
}

View File

@ -0,0 +1,21 @@
interface IPageHistoryUser {
id: string;
name: string;
avatarUrl: string;
}
export interface IPageHistory {
id: string;
pageId: string;
title: string;
content?: any;
slug: string;
icon: string;
coverPhoto: string;
version: number;
lastUpdatedById: string;
workspaceId: string;
createdAt: string;
updatedAt: string;
lastUpdatedBy: IPageHistoryUser;
}