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:
File diff suppressed because it is too large
Load Diff
70
app/(dashboard)/ajustes/data.ts
Normal file
70
app/(dashboard)/ajustes/data.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { apiTokens } from "@/db/schema";
|
||||
import { db, schema } from "@/lib/db";
|
||||
|
||||
export interface UserPreferences {
|
||||
disableMagnetlines: boolean;
|
||||
}
|
||||
|
||||
export interface ApiToken {
|
||||
id: string;
|
||||
name: string;
|
||||
tokenPrefix: string;
|
||||
lastUsedAt: Date | null;
|
||||
lastUsedIp: string | null;
|
||||
createdAt: Date;
|
||||
expiresAt: Date | null;
|
||||
revokedAt: Date | null;
|
||||
}
|
||||
|
||||
export async function fetchAuthProvider(userId: string): Promise<string> {
|
||||
const userAccount = await db.query.account.findFirst({
|
||||
where: eq(schema.account.userId, userId),
|
||||
});
|
||||
return userAccount?.providerId || "credential";
|
||||
}
|
||||
|
||||
export async function fetchUserPreferences(
|
||||
userId: string,
|
||||
): Promise<UserPreferences | null> {
|
||||
const result = await db
|
||||
.select({
|
||||
disableMagnetlines: schema.userPreferences.disableMagnetlines,
|
||||
})
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
return result[0] || null;
|
||||
}
|
||||
|
||||
export async function fetchApiTokens(userId: string): Promise<ApiToken[]> {
|
||||
return db
|
||||
.select({
|
||||
id: apiTokens.id,
|
||||
name: apiTokens.name,
|
||||
tokenPrefix: apiTokens.tokenPrefix,
|
||||
lastUsedAt: apiTokens.lastUsedAt,
|
||||
lastUsedIp: apiTokens.lastUsedIp,
|
||||
createdAt: apiTokens.createdAt,
|
||||
expiresAt: apiTokens.expiresAt,
|
||||
revokedAt: apiTokens.revokedAt,
|
||||
})
|
||||
.from(apiTokens)
|
||||
.where(eq(apiTokens.userId, userId))
|
||||
.orderBy(desc(apiTokens.createdAt));
|
||||
}
|
||||
|
||||
export async function fetchAjustesPageData(userId: string) {
|
||||
const [authProvider, userPreferences, userApiTokens] = await Promise.all([
|
||||
fetchAuthProvider(userId),
|
||||
fetchUserPreferences(userId),
|
||||
fetchApiTokens(userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
authProvider,
|
||||
userPreferences,
|
||||
userApiTokens,
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
import PageDescription from "@/components/page-description";
|
||||
import { RiSettingsLine } from "@remixicon/react";
|
||||
import PageDescription from "@/components/page-description";
|
||||
|
||||
export const metadata = {
|
||||
title: "Ajustes | Opensheets",
|
||||
title: "Ajustes | Opensheets",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiSettingsLine />}
|
||||
title="Ajustes"
|
||||
subtitle="Gerencie informações da conta, segurança e outras opções para otimizar sua experiência."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
return (
|
||||
<section className="space-y-6 px-6">
|
||||
<PageDescription
|
||||
icon={<RiSettingsLine />}
|
||||
title="Ajustes"
|
||||
subtitle="Gerencie informações da conta, segurança e outras opções para otimizar sua experiência."
|
||||
/>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,180 +1,148 @@
|
||||
import { ApiTokensForm } from "@/components/ajustes/api-tokens-form";
|
||||
import { DeleteAccountForm } from "@/components/ajustes/delete-account-form";
|
||||
import { UpdateEmailForm } from "@/components/ajustes/update-email-form";
|
||||
import { UpdateNameForm } from "@/components/ajustes/update-name-form";
|
||||
import { UpdatePasswordForm } from "@/components/ajustes/update-password-form";
|
||||
import { PreferencesForm } from "@/components/ajustes/preferences-form";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { auth } from "@/lib/auth/config";
|
||||
import { db, schema } from "@/lib/db";
|
||||
import { apiTokens } from "@/db/schema";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { ApiTokensForm } from "@/components/ajustes/api-tokens-form";
|
||||
import { DeleteAccountForm } from "@/components/ajustes/delete-account-form";
|
||||
import { PreferencesForm } from "@/components/ajustes/preferences-form";
|
||||
import { UpdateEmailForm } from "@/components/ajustes/update-email-form";
|
||||
import { UpdateNameForm } from "@/components/ajustes/update-name-form";
|
||||
import { UpdatePasswordForm } from "@/components/ajustes/update-password-form";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { auth } from "@/lib/auth/config";
|
||||
|
||||
import { fetchAjustesPageData } from "./data";
|
||||
|
||||
export default async function Page() {
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers(),
|
||||
});
|
||||
const session = await auth.api.getSession({
|
||||
headers: await headers(),
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
redirect("/");
|
||||
}
|
||||
if (!session?.user) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
const userName = session.user.name || "";
|
||||
const userEmail = session.user.email || "";
|
||||
const userName = session.user.name || "";
|
||||
const userEmail = session.user.email || "";
|
||||
|
||||
// Detectar método de autenticação (Google OAuth vs E-mail/Senha)
|
||||
const userAccount = await db.query.account.findFirst({
|
||||
where: eq(schema.account.userId, session.user.id),
|
||||
});
|
||||
const { authProvider, userPreferences, userApiTokens } =
|
||||
await fetchAjustesPageData(session.user.id);
|
||||
|
||||
// Buscar preferências do usuário
|
||||
const userPreferencesResult = await db
|
||||
.select({
|
||||
disableMagnetlines: schema.userPreferences.disableMagnetlines,
|
||||
})
|
||||
.from(schema.userPreferences)
|
||||
.where(eq(schema.userPreferences.userId, session.user.id))
|
||||
.limit(1);
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Tabs defaultValue="preferencias" className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="preferencias">Preferências</TabsTrigger>
|
||||
<TabsTrigger value="dispositivos">Dispositivos</TabsTrigger>
|
||||
<TabsTrigger value="nome">Alterar nome</TabsTrigger>
|
||||
<TabsTrigger value="senha">Alterar senha</TabsTrigger>
|
||||
<TabsTrigger value="email">Alterar e-mail</TabsTrigger>
|
||||
<TabsTrigger value="deletar" className="text-destructive">
|
||||
Deletar conta
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
const userPreferences = userPreferencesResult[0] || null;
|
||||
<TabsContent value="preferencias" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Preferências</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Personalize sua experiência no Opensheets ajustando as
|
||||
configurações de acordo com suas necessidades.
|
||||
</p>
|
||||
</div>
|
||||
<PreferencesForm
|
||||
disableMagnetlines={
|
||||
userPreferences?.disableMagnetlines ?? false
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
// Se o providerId for "google", o usuário usa Google OAuth
|
||||
const authProvider = userAccount?.providerId || "credential";
|
||||
<TabsContent value="dispositivos" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">OpenSheets Companion</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Conecte o app Android OpenSheets Companion para capturar
|
||||
automaticamente notificações de transações financeiras e
|
||||
enviá-las para sua caixa de entrada.
|
||||
</p>
|
||||
</div>
|
||||
<ApiTokensForm tokens={userApiTokens} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
// Buscar tokens de API do usuário
|
||||
const userApiTokens = await db
|
||||
.select({
|
||||
id: apiTokens.id,
|
||||
name: apiTokens.name,
|
||||
tokenPrefix: apiTokens.tokenPrefix,
|
||||
lastUsedAt: apiTokens.lastUsedAt,
|
||||
lastUsedIp: apiTokens.lastUsedIp,
|
||||
createdAt: apiTokens.createdAt,
|
||||
expiresAt: apiTokens.expiresAt,
|
||||
revokedAt: apiTokens.revokedAt,
|
||||
})
|
||||
.from(apiTokens)
|
||||
.where(eq(apiTokens.userId, session.user.id))
|
||||
.orderBy(desc(apiTokens.createdAt));
|
||||
<TabsContent value="nome" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar nome</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Atualize como seu nome aparece no Opensheets. Esse nome pode
|
||||
ser exibido em diferentes seções do app e em comunicações.
|
||||
</p>
|
||||
</div>
|
||||
<UpdateNameForm currentName={userName} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<Tabs defaultValue="preferencias" className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="preferencias">Preferências</TabsTrigger>
|
||||
<TabsTrigger value="dispositivos">Dispositivos</TabsTrigger>
|
||||
<TabsTrigger value="nome">Alterar nome</TabsTrigger>
|
||||
<TabsTrigger value="senha">Alterar senha</TabsTrigger>
|
||||
<TabsTrigger value="email">Alterar e-mail</TabsTrigger>
|
||||
<TabsTrigger value="deletar" className="text-destructive">
|
||||
Deletar conta
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="senha" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar senha</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Defina uma nova senha para sua conta. Guarde-a em local
|
||||
seguro.
|
||||
</p>
|
||||
</div>
|
||||
<UpdatePasswordForm authProvider={authProvider} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="preferencias" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Preferências</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Personalize sua experiência no Opensheets ajustando as
|
||||
configurações de acordo com suas necessidades.
|
||||
</p>
|
||||
</div>
|
||||
<PreferencesForm
|
||||
disableMagnetlines={
|
||||
userPreferences?.disableMagnetlines ?? false
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="email" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar e-mail</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Atualize o e-mail associado à sua conta. Você precisará
|
||||
confirmar os links enviados para o novo e também para o e-mail
|
||||
atual (quando aplicável) para concluir a alteração.
|
||||
</p>
|
||||
</div>
|
||||
<UpdateEmailForm
|
||||
currentEmail={userEmail}
|
||||
authProvider={authProvider}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="dispositivos" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">OpenSheets Companion</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Conecte o app Android OpenSheets Companion para capturar
|
||||
automaticamente notificações de transações financeiras e
|
||||
enviá-las para sua caixa de entrada.
|
||||
</p>
|
||||
</div>
|
||||
<ApiTokensForm tokens={userApiTokens} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="nome" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar nome</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Atualize como seu nome aparece no Opensheets. Esse nome pode
|
||||
ser exibido em diferentes seções do app e em comunicações.
|
||||
</p>
|
||||
</div>
|
||||
<UpdateNameForm currentName={userName} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="senha" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar senha</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Defina uma nova senha para sua conta. Guarde-a em local
|
||||
seguro.
|
||||
</p>
|
||||
</div>
|
||||
<UpdatePasswordForm authProvider={authProvider} />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="email" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1">Alterar e-mail</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Atualize o e-mail associado à sua conta. Você precisará
|
||||
confirmar os links enviados para o novo e também para o e-mail
|
||||
atual (quando aplicável) para concluir a alteração.
|
||||
</p>
|
||||
</div>
|
||||
<UpdateEmailForm
|
||||
currentEmail={userEmail}
|
||||
authProvider={authProvider}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="deletar" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1 text-destructive">
|
||||
Deletar conta
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Ao prosseguir, sua conta e todos os dados associados serão
|
||||
excluídos de forma irreversível.
|
||||
</p>
|
||||
</div>
|
||||
<DeleteAccountForm />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
<TabsContent value="deletar" className="mt-4">
|
||||
<Card className="p-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold mb-1 text-destructive">
|
||||
Deletar conta
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Ao prosseguir, sua conta e todos os dados associados serão
|
||||
excluídos de forma irreversível.
|
||||
</p>
|
||||
</div>
|
||||
<DeleteAccountForm />
|
||||
</div>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user