mirror of
https://github.com/marcogll/TaxHacker_s23.git
synced 2026-01-14 04:55:27 +00:00
feat: invoice generator
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { userFormSchema } from "@/forms/users"
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { uploadStaticImage } from "@/lib/uploads"
|
||||
import { codeFromName, randomHexColor } from "@/lib/utils"
|
||||
import { createCategory, deleteCategory, updateCategory } from "@/models/categories"
|
||||
import { createCurrency, deleteCurrency, updateCurrency } from "@/models/currencies"
|
||||
@@ -19,7 +20,7 @@ import { SettingsMap, updateSettings } from "@/models/settings"
|
||||
import { updateUser } from "@/models/users"
|
||||
import { Prisma, User } from "@/prisma/client"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { redirect } from "next/navigation"
|
||||
import path from "path"
|
||||
|
||||
export async function saveSettingsAction(
|
||||
_prevState: ActionState<SettingsMap> | null,
|
||||
@@ -40,8 +41,7 @@ export async function saveSettingsAction(
|
||||
}
|
||||
|
||||
revalidatePath("/settings")
|
||||
redirect("/settings")
|
||||
// return { success: true }
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function saveProfileAction(
|
||||
@@ -55,12 +55,47 @@ export async function saveProfileAction(
|
||||
return { success: false, error: validatedForm.error.message }
|
||||
}
|
||||
|
||||
// Upload avatar
|
||||
let avatarUrl = user.avatar
|
||||
const avatarFile = formData.get("avatar") as File | null
|
||||
if (avatarFile instanceof File && avatarFile.size > 0) {
|
||||
try {
|
||||
const uploadedAvatarPath = await uploadStaticImage(user, avatarFile, "avatar.webp", 500, 500)
|
||||
avatarUrl = `/files/static/${path.basename(uploadedAvatarPath)}`
|
||||
} catch (error) {
|
||||
return { success: false, error: "Failed to upload avatar: " + error }
|
||||
}
|
||||
}
|
||||
|
||||
// Upload business logo
|
||||
let businessLogoUrl = user.businessLogo
|
||||
const businessLogoFile = formData.get("businessLogo") as File | null
|
||||
if (businessLogoFile instanceof File && businessLogoFile.size > 0) {
|
||||
try {
|
||||
const uploadedBusinessLogoPath = await uploadStaticImage(user, businessLogoFile, "businessLogo.png", 500, 500)
|
||||
businessLogoUrl = `/files/static/${path.basename(uploadedBusinessLogoPath)}`
|
||||
} catch (error) {
|
||||
return { success: false, error: "Failed to upload business logo: " + error }
|
||||
}
|
||||
}
|
||||
|
||||
// Update user
|
||||
await updateUser(user.id, {
|
||||
name: validatedForm.data.name,
|
||||
name: validatedForm.data.name !== undefined ? validatedForm.data.name : user.name,
|
||||
avatar: avatarUrl,
|
||||
businessName: validatedForm.data.businessName !== undefined ? validatedForm.data.businessName : user.businessName,
|
||||
businessAddress:
|
||||
validatedForm.data.businessAddress !== undefined ? validatedForm.data.businessAddress : user.businessAddress,
|
||||
businessBankDetails:
|
||||
validatedForm.data.businessBankDetails !== undefined
|
||||
? validatedForm.data.businessBankDetails
|
||||
: user.businessBankDetails,
|
||||
businessLogo: businessLogoUrl,
|
||||
})
|
||||
|
||||
revalidatePath("/settings/profile")
|
||||
redirect("/settings/profile")
|
||||
revalidatePath("/settings/business")
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function addProjectAction(userId: string, data: Prisma.ProjectCreateInput) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { ActionState } from "@/lib/actions"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
import { prisma } from "@/lib/db"
|
||||
import { getUserUploadsDirectory } from "@/lib/files"
|
||||
import { getUserUploadsDirectory, safePathJoin } from "@/lib/files"
|
||||
import { MODEL_BACKUP, modelFromJSON } from "@/models/backups"
|
||||
import fs from "fs/promises"
|
||||
import JSZip from "jszip"
|
||||
@@ -22,7 +22,7 @@ export async function restoreBackupAction(
|
||||
formData: FormData
|
||||
): Promise<ActionState<BackupRestoreResult>> {
|
||||
const user = await getCurrentUser()
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
const userUploadsDirectory = getUserUploadsDirectory(user)
|
||||
const file = formData.get("file") as File
|
||||
|
||||
if (!file || file.size === 0) {
|
||||
@@ -98,7 +98,7 @@ export async function restoreBackupAction(
|
||||
},
|
||||
})
|
||||
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
const userUploadsDirectory = getUserUploadsDirectory(user)
|
||||
|
||||
for (const file of files) {
|
||||
const filePathWithoutPrefix = path.normalize(file.path.replace(/^.*\/uploads\//, ""))
|
||||
@@ -110,7 +110,7 @@ export async function restoreBackupAction(
|
||||
}
|
||||
|
||||
const fileContents = await zipFile.async("nodebuffer")
|
||||
const fullFilePath = path.join(userUploadsDirectory, filePathWithoutPrefix)
|
||||
const fullFilePath = safePathJoin(userUploadsDirectory, filePathWithoutPrefix)
|
||||
if (!fullFilePath.startsWith(path.normalize(userUploadsDirectory))) {
|
||||
console.error(`Attempted path traversal detected for file ${file.path}`)
|
||||
continue
|
||||
|
||||
@@ -11,7 +11,7 @@ const BACKUP_VERSION = "1.0"
|
||||
|
||||
export async function GET() {
|
||||
const user = await getCurrentUser()
|
||||
const userUploadsDirectory = await getUserUploadsDirectory(user)
|
||||
const userUploadsDirectory = getUserUploadsDirectory(user)
|
||||
|
||||
try {
|
||||
const zip = new JSZip()
|
||||
|
||||
14
app/(app)/settings/business/page.tsx
Normal file
14
app/(app)/settings/business/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import BusinessSettingsForm from "@/components/settings/business-settings-form"
|
||||
import { getCurrentUser } from "@/lib/auth"
|
||||
|
||||
export default async function BusinessSettingsPage() {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full max-w-2xl">
|
||||
<BusinessSettingsForm user={user} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -16,6 +16,10 @@ const settingsCategories = [
|
||||
title: "Profile & Plan",
|
||||
href: "/settings/profile",
|
||||
},
|
||||
{
|
||||
title: "Business Details",
|
||||
href: "/settings/business",
|
||||
},
|
||||
{
|
||||
title: "LLM settings",
|
||||
href: "/settings/llm",
|
||||
|
||||
Reference in New Issue
Block a user