forked from git.gladyson/openmonetis
refactor: migrate from ESLint to Biome and extract SQL queries to data.ts
- Replace ESLint with Biome for linting and formatting - Configure Biome with tabs, double quotes, and organized imports - Move all SQL/Drizzle queries from page.tsx files to data.ts files - Create new data.ts files for: ajustes, dashboard, relatorios/categorias - Update existing data.ts files: extrato, fatura (add lancamentos queries) - Remove all drizzle-orm imports from page.tsx files - Update README.md with new tooling info Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,59 +1,64 @@
|
||||
"use server";
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { anotacoes } from "@/db/schema";
|
||||
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||
import type { ActionResult } from "@/lib/actions/types";
|
||||
import { uuidSchema } from "@/lib/schemas/common";
|
||||
import { db } from "@/lib/db";
|
||||
import { getUser } from "@/lib/auth/server";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { uuidSchema } from "@/lib/schemas/common";
|
||||
|
||||
const taskSchema = z.object({
|
||||
id: z.string(),
|
||||
text: z.string().min(1, "O texto da tarefa não pode estar vazio."),
|
||||
completed: z.boolean(),
|
||||
id: z.string(),
|
||||
text: z.string().min(1, "O texto da tarefa não pode estar vazio."),
|
||||
completed: z.boolean(),
|
||||
});
|
||||
|
||||
const noteBaseSchema = z.object({
|
||||
title: z
|
||||
.string({ message: "Informe o título da anotação." })
|
||||
.trim()
|
||||
.min(1, "Informe o título da anotação.")
|
||||
.max(30, "O título deve ter no máximo 30 caracteres."),
|
||||
description: z
|
||||
.string({ message: "Informe o conteúdo da anotação." })
|
||||
.trim()
|
||||
.max(350, "O conteúdo deve ter no máximo 350 caracteres.")
|
||||
.optional()
|
||||
.default(""),
|
||||
type: z.enum(["nota", "tarefa"], {
|
||||
message: "O tipo deve ser 'nota' ou 'tarefa'.",
|
||||
}),
|
||||
tasks: z.array(taskSchema).optional().default([]),
|
||||
}).refine(
|
||||
(data) => {
|
||||
// Se for nota, a descrição é obrigatória
|
||||
if (data.type === "nota") {
|
||||
return data.description.trim().length > 0;
|
||||
}
|
||||
// Se for tarefa, deve ter pelo menos uma tarefa
|
||||
if (data.type === "tarefa") {
|
||||
return data.tasks && data.tasks.length > 0;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Notas precisam de descrição e Tarefas precisam de ao menos uma tarefa.",
|
||||
}
|
||||
);
|
||||
const noteBaseSchema = z
|
||||
.object({
|
||||
title: z
|
||||
.string({ message: "Informe o título da anotação." })
|
||||
.trim()
|
||||
.min(1, "Informe o título da anotação.")
|
||||
.max(30, "O título deve ter no máximo 30 caracteres."),
|
||||
description: z
|
||||
.string({ message: "Informe o conteúdo da anotação." })
|
||||
.trim()
|
||||
.max(350, "O conteúdo deve ter no máximo 350 caracteres.")
|
||||
.optional()
|
||||
.default(""),
|
||||
type: z.enum(["nota", "tarefa"], {
|
||||
message: "O tipo deve ser 'nota' ou 'tarefa'.",
|
||||
}),
|
||||
tasks: z.array(taskSchema).optional().default([]),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// Se for nota, a descrição é obrigatória
|
||||
if (data.type === "nota") {
|
||||
return data.description.trim().length > 0;
|
||||
}
|
||||
// Se for tarefa, deve ter pelo menos uma tarefa
|
||||
if (data.type === "tarefa") {
|
||||
return data.tasks && data.tasks.length > 0;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Notas precisam de descrição e Tarefas precisam de ao menos uma tarefa.",
|
||||
},
|
||||
);
|
||||
|
||||
const createNoteSchema = noteBaseSchema;
|
||||
const updateNoteSchema = noteBaseSchema.and(z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
}));
|
||||
const updateNoteSchema = noteBaseSchema.and(
|
||||
z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
}),
|
||||
);
|
||||
const deleteNoteSchema = z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
id: uuidSchema("Anotação"),
|
||||
});
|
||||
|
||||
type NoteCreateInput = z.infer<typeof createNoteSchema>;
|
||||
@@ -61,126 +66,130 @@ type NoteUpdateInput = z.infer<typeof updateNoteSchema>;
|
||||
type NoteDeleteInput = z.infer<typeof deleteNoteSchema>;
|
||||
|
||||
export async function createNoteAction(
|
||||
input: NoteCreateInput
|
||||
input: NoteCreateInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = createNoteSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = createNoteSchema.parse(input);
|
||||
|
||||
await db.insert(anotacoes).values({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
tasks: data.tasks && data.tasks.length > 0 ? JSON.stringify(data.tasks) : null,
|
||||
userId: user.id,
|
||||
});
|
||||
await db.insert(anotacoes).values({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
tasks:
|
||||
data.tasks && data.tasks.length > 0 ? JSON.stringify(data.tasks) : null,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("anotacoes");
|
||||
|
||||
return { success: true, message: "Anotação criada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return { success: true, message: "Anotação criada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateNoteAction(
|
||||
input: NoteUpdateInput
|
||||
input: NoteUpdateInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updateNoteSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = updateNoteSchema.parse(input);
|
||||
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.set({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
tasks: data.tasks && data.tasks.length > 0 ? JSON.stringify(data.tasks) : null,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.set({
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
type: data.type,
|
||||
tasks:
|
||||
data.tasks && data.tasks.length > 0
|
||||
? JSON.stringify(data.tasks)
|
||||
: null,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("anotacoes");
|
||||
|
||||
return { success: true, message: "Anotação atualizada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return { success: true, message: "Anotação atualizada com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteNoteAction(
|
||||
input: NoteDeleteInput
|
||||
input: NoteDeleteInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = deleteNoteSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = deleteNoteSchema.parse(input);
|
||||
|
||||
const [deleted] = await db
|
||||
.delete(anotacoes)
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
const [deleted] = await db
|
||||
.delete(anotacoes)
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
if (!deleted) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("anotacoes");
|
||||
|
||||
return { success: true, message: "Anotação removida com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return { success: true, message: "Anotação removida com sucesso." };
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
const arquivarNoteSchema = z.object({
|
||||
id: uuidSchema("Anotação"),
|
||||
arquivada: z.boolean(),
|
||||
id: uuidSchema("Anotação"),
|
||||
arquivada: z.boolean(),
|
||||
});
|
||||
|
||||
type NoteArquivarInput = z.infer<typeof arquivarNoteSchema>;
|
||||
|
||||
export async function arquivarAnotacaoAction(
|
||||
input: NoteArquivarInput
|
||||
input: NoteArquivarInput,
|
||||
): Promise<ActionResult> {
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = arquivarNoteSchema.parse(input);
|
||||
try {
|
||||
const user = await getUser();
|
||||
const data = arquivarNoteSchema.parse(input);
|
||||
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.set({
|
||||
arquivada: data.arquivada,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
const [updated] = await db
|
||||
.update(anotacoes)
|
||||
.set({
|
||||
arquivada: data.arquivada,
|
||||
})
|
||||
.where(and(eq(anotacoes.id, data.id), eq(anotacoes.userId, user.id)))
|
||||
.returning({ id: anotacoes.id });
|
||||
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
if (!updated) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Anotação não encontrada.",
|
||||
};
|
||||
}
|
||||
|
||||
revalidateForEntity("anotacoes");
|
||||
revalidateForEntity("anotacoes");
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: data.arquivada
|
||||
? "Anotação arquivada com sucesso."
|
||||
: "Anotação desarquivada com sucesso."
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: data.arquivada
|
||||
? "Anotação arquivada com sucesso."
|
||||
: "Anotação desarquivada com sucesso.",
|
||||
};
|
||||
} catch (error) {
|
||||
return handleActionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { getUserId } from "@/lib/auth/server";
|
||||
import { fetchArquivadasForUser } from "../data";
|
||||
|
||||
export default async function ArquivadasPage() {
|
||||
const userId = await getUserId();
|
||||
const notes = await fetchArquivadasForUser(userId);
|
||||
const userId = await getUserId();
|
||||
const notes = await fetchArquivadasForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<NotesPage notes={notes} isArquivadas={true} />
|
||||
</main>
|
||||
);
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<NotesPage notes={notes} isArquivadas={true} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,81 +1,89 @@
|
||||
import { anotacoes, type Anotacao } from "@/db/schema";
|
||||
import { db } from "@/lib/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { type Anotacao, anotacoes } from "@/db/schema";
|
||||
import { db } from "@/lib/db";
|
||||
|
||||
export type Task = {
|
||||
id: string;
|
||||
text: string;
|
||||
completed: boolean;
|
||||
id: string;
|
||||
text: string;
|
||||
completed: boolean;
|
||||
};
|
||||
|
||||
export type NoteData = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: "nota" | "tarefa";
|
||||
tasks?: Task[];
|
||||
arquivada: boolean;
|
||||
createdAt: string;
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: "nota" | "tarefa";
|
||||
tasks?: Task[];
|
||||
arquivada: boolean;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export async function fetchNotesForUser(userId: string): Promise<NoteData[]> {
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, false)),
|
||||
orderBy: (note: typeof anotacoes.$inferSelect, { desc }: { desc: (field: unknown) => unknown }) => [desc(note.createdAt)],
|
||||
});
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, false)),
|
||||
orderBy: (
|
||||
note: typeof anotacoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(note.createdAt)],
|
||||
});
|
||||
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
let tasks: Task[] | undefined;
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
let tasks: Task[] | undefined;
|
||||
|
||||
// Parse tasks if they exist
|
||||
if (note.tasks) {
|
||||
try {
|
||||
tasks = JSON.parse(note.tasks);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tasks for note", note.id, error);
|
||||
tasks = undefined;
|
||||
}
|
||||
}
|
||||
// Parse tasks if they exist
|
||||
if (note.tasks) {
|
||||
try {
|
||||
tasks = JSON.parse(note.tasks);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tasks for note", note.id, error);
|
||||
tasks = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: note.id,
|
||||
title: (note.title ?? "").trim(),
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
return {
|
||||
id: note.id,
|
||||
title: (note.title ?? "").trim(),
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchArquivadasForUser(userId: string): Promise<NoteData[]> {
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, true)),
|
||||
orderBy: (note: typeof anotacoes.$inferSelect, { desc }: { desc: (field: unknown) => unknown }) => [desc(note.createdAt)],
|
||||
});
|
||||
export async function fetchArquivadasForUser(
|
||||
userId: string,
|
||||
): Promise<NoteData[]> {
|
||||
const noteRows = await db.query.anotacoes.findMany({
|
||||
where: and(eq(anotacoes.userId, userId), eq(anotacoes.arquivada, true)),
|
||||
orderBy: (
|
||||
note: typeof anotacoes.$inferSelect,
|
||||
{ desc }: { desc: (field: unknown) => unknown },
|
||||
) => [desc(note.createdAt)],
|
||||
});
|
||||
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
let tasks: Task[] | undefined;
|
||||
return noteRows.map((note: Anotacao) => {
|
||||
let tasks: Task[] | undefined;
|
||||
|
||||
// Parse tasks if they exist
|
||||
if (note.tasks) {
|
||||
try {
|
||||
tasks = JSON.parse(note.tasks);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tasks for note", note.id, error);
|
||||
tasks = undefined;
|
||||
}
|
||||
}
|
||||
// Parse tasks if they exist
|
||||
if (note.tasks) {
|
||||
try {
|
||||
tasks = JSON.parse(note.tasks);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse tasks for note", note.id, error);
|
||||
tasks = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: note.id,
|
||||
title: (note.title ?? "").trim(),
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
return {
|
||||
id: note.id,
|
||||
title: (note.title ?? "").trim(),
|
||||
description: (note.description ?? "").trim(),
|
||||
type: (note.type ?? "nota") as "nota" | "tarefa",
|
||||
tasks,
|
||||
arquivada: note.arquivada,
|
||||
createdAt: note.createdAt.toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import PageDescription from "@/components/page-description";
|
||||
import { RiTodoLine } from "@remixicon/react";
|
||||
import PageDescription from "@/components/page-description";
|
||||
|
||||
export const metadata = {
|
||||
title: "Anotações | Opensheets",
|
||||
title: "Anotações | Opensheets",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiTodoLine />}
|
||||
title="Notas"
|
||||
subtitle="Gerencie suas anotações e mantenha o controle sobre suas ideias e tarefas."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiTodoLine />}
|
||||
title="Notas"
|
||||
subtitle="Gerencie suas anotações e mantenha o controle sobre suas ideias e tarefas."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,47 +5,44 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
* Layout: Header com botão + Grid de cards de notas
|
||||
*/
|
||||
export default function AnotacoesLoading() {
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<div className="w-full space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-8 w-32 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-40 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<div className="w-full space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-8 w-32 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-10 w-40 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
|
||||
{/* Grid de cards de notas */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-2xl border p-4 space-y-3"
|
||||
>
|
||||
{/* Título */}
|
||||
<Skeleton className="h-6 w-3/4 rounded-2xl bg-foreground/10" />
|
||||
{/* Grid de cards de notas */}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className="rounded-2xl border p-4 space-y-3">
|
||||
{/* Título */}
|
||||
<Skeleton className="h-6 w-3/4 rounded-2xl bg-foreground/10" />
|
||||
|
||||
{/* Conteúdo (3-4 linhas) */}
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-2/3 rounded-2xl bg-foreground/10" />
|
||||
{i % 2 === 0 && (
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
)}
|
||||
</div>
|
||||
{/* Conteúdo (3-4 linhas) */}
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="h-4 w-2/3 rounded-2xl bg-foreground/10" />
|
||||
{i % 2 === 0 && (
|
||||
<Skeleton className="h-4 w-full rounded-2xl bg-foreground/10" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer com data e ações */}
|
||||
<div className="flex items-center justify-between pt-2 border-t">
|
||||
<Skeleton className="h-3 w-24 rounded-2xl bg-foreground/10" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-8 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="size-8 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
{/* Footer com data e ações */}
|
||||
<div className="flex items-center justify-between pt-2 border-t">
|
||||
<Skeleton className="h-3 w-24 rounded-2xl bg-foreground/10" />
|
||||
<div className="flex gap-1">
|
||||
<Skeleton className="size-8 rounded-2xl bg-foreground/10" />
|
||||
<Skeleton className="size-8 rounded-2xl bg-foreground/10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { getUserId } from "@/lib/auth/server";
|
||||
import { fetchNotesForUser } from "./data";
|
||||
|
||||
export default async function Page() {
|
||||
const userId = await getUserId();
|
||||
const notes = await fetchNotesForUser(userId);
|
||||
const userId = await getUserId();
|
||||
const notes = await fetchNotesForUser(userId);
|
||||
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<NotesPage notes={notes} />
|
||||
</main>
|
||||
);
|
||||
return (
|
||||
<main className="flex flex-col items-start gap-6">
|
||||
<NotesPage notes={notes} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user