first commit
This commit is contained in:
195
app/api/admin/categories/[id]/route.ts
Normal file
195
app/api/admin/categories/[id]/route.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
// Check if category exists
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!existingCategory) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Category not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check if category has products
|
||||
if (existingCategory._count.products > 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Cannot delete category with associated products',
|
||||
productCount: existingCategory._count.products
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Delete the category
|
||||
await prisma.category.delete({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
message: 'Category deleted successfully',
|
||||
deletedCategory: {
|
||||
id: existingCategory.id,
|
||||
name: existingCategory.name
|
||||
}
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error deleting category:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete category' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const body = await request.json()
|
||||
const { name, description, image, isActive } = body
|
||||
|
||||
// Check if category exists
|
||||
const existingCategory = await prisma.category.findUnique({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
if (!existingCategory) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Category not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// If name is being updated, check for uniqueness
|
||||
if (name && name !== existingCategory.name) {
|
||||
const nameExists = await prisma.category.findUnique({
|
||||
where: { name }
|
||||
})
|
||||
|
||||
if (nameExists) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Category name already exists' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the category
|
||||
const updatedCategory = await prisma.category.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(name !== undefined && { name }),
|
||||
...(description !== undefined && { description }),
|
||||
...(image !== undefined && { image }),
|
||||
...(isActive !== undefined && { isActive })
|
||||
},
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(updatedCategory)
|
||||
} catch (error: any) {
|
||||
console.error('Error updating category:', error)
|
||||
|
||||
// Handle Prisma unique constraint errors
|
||||
if (error.code === 'P2002') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Category name already exists' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update category' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const category = await prisma.category.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
},
|
||||
products: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
price: true,
|
||||
isActive: true
|
||||
},
|
||||
take: 10 // Limit to first 10 products for preview
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!category) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Category not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(category)
|
||||
} catch (error: any) {
|
||||
console.error('Error fetching category:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch category' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
73
app/api/admin/categories/route.ts
Normal file
73
app/api/admin/categories/route.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const search = searchParams.get('search')
|
||||
|
||||
const categories = await prisma.category.findMany({
|
||||
where: search ? {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: 'insensitive'
|
||||
}
|
||||
} : {},
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(categories)
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch categories' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { name, description, image, isActive } = body
|
||||
|
||||
const category = await prisma.category.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
image,
|
||||
isActive
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(category)
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create category' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
43
app/api/admin/commission-settings/route.ts
Normal file
43
app/api/admin/commission-settings/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const settings = await prisma.commissionSettings.findMany({
|
||||
orderBy: { level: 'asc' },
|
||||
})
|
||||
|
||||
return NextResponse.json(settings)
|
||||
} catch (error) {
|
||||
console.error('Commission settings API error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { level, percentage, isActive } = await request.json()
|
||||
|
||||
const setting = await prisma.commissionSettings.upsert({
|
||||
where: { level },
|
||||
update: { percentage, isActive },
|
||||
create: { level, percentage, isActive },
|
||||
})
|
||||
|
||||
return NextResponse.json(setting)
|
||||
} catch (error) {
|
||||
console.error('Create commission setting error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
37
app/api/admin/commissions/[id]/route.ts
Normal file
37
app/api/admin/commissions/[id]/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: commissionId } = await params
|
||||
const { status } = await request.json()
|
||||
|
||||
// Validate status
|
||||
if (!['PENDING', 'APPROVED', 'PAID', 'CANCELLED'].includes(status)) {
|
||||
return NextResponse.json({ error: 'Invalid status' }, { status: 400 })
|
||||
}
|
||||
|
||||
const commission = await prisma.commission.update({
|
||||
where: { id: commissionId },
|
||||
data: { status }
|
||||
})
|
||||
|
||||
return NextResponse.json(commission)
|
||||
} catch (error) {
|
||||
console.error('Error updating commission:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update commission' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
77
app/api/admin/commissions/route.ts
Normal file
77
app/api/admin/commissions/route.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const url = new URL(request.url)
|
||||
const status = url.searchParams.get('status')
|
||||
const type = url.searchParams.get('type')
|
||||
const level = url.searchParams.get('level')
|
||||
const page = parseInt(url.searchParams.get('page') || '1')
|
||||
const limit = parseInt(url.searchParams.get('limit') || '50')
|
||||
|
||||
const where: any = {}
|
||||
|
||||
if (status && status !== 'all') {
|
||||
where.status = status
|
||||
}
|
||||
|
||||
if (type && type !== 'all') {
|
||||
where.type = type
|
||||
}
|
||||
|
||||
if (level && level !== 'all') {
|
||||
where.level = parseInt(level)
|
||||
}
|
||||
|
||||
const commissions = await prisma.commission.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
},
|
||||
fromUser: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
skip: (page - 1) * limit,
|
||||
take: limit
|
||||
})
|
||||
|
||||
const total = await prisma.commission.count({ where })
|
||||
|
||||
return NextResponse.json({
|
||||
commissions: commissions || [],
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching admin commissions:', error)
|
||||
return NextResponse.json({
|
||||
commissions: [],
|
||||
pagination: { page: 1, limit: 50, total: 0, pages: 0 }
|
||||
})
|
||||
}
|
||||
}
|
||||
36
app/api/admin/commissions/settings/[id]/route.ts
Normal file
36
app/api/admin/commissions/settings/[id]/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: settingId } = await params
|
||||
const { level, percentage, isActive } = await request.json()
|
||||
|
||||
const setting = await prisma.commissionSettings.update({
|
||||
where: { id: settingId },
|
||||
data: {
|
||||
level,
|
||||
percentage,
|
||||
isActive
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(setting)
|
||||
} catch (error) {
|
||||
console.error('Error updating commission setting:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update commission setting' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
41
app/api/admin/commissions/settings/route.ts
Normal file
41
app/api/admin/commissions/settings/route.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
let settings = await prisma.commissionSettings.findMany({
|
||||
orderBy: { level: 'asc' }
|
||||
})
|
||||
|
||||
// If no settings exist, create default ones
|
||||
if (settings.length === 0) {
|
||||
const defaultSettings = [
|
||||
{ level: 1, percentage: 10, isActive: true },
|
||||
{ level: 2, percentage: 5, isActive: true },
|
||||
{ level: 3, percentage: 3, isActive: true },
|
||||
{ level: 4, percentage: 2, isActive: true },
|
||||
{ level: 5, percentage: 1, isActive: true }
|
||||
]
|
||||
|
||||
await prisma.commissionSettings.createMany({
|
||||
data: defaultSettings
|
||||
})
|
||||
|
||||
settings = await prisma.commissionSettings.findMany({
|
||||
orderBy: { level: 'asc' }
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ settings: settings || [] })
|
||||
} catch (error) {
|
||||
console.error('Error fetching commission settings:', error)
|
||||
return NextResponse.json({ settings: [] })
|
||||
}
|
||||
}
|
||||
66
app/api/admin/commissions/stats/route.ts
Normal file
66
app/api/admin/commissions/stats/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get total commissions count
|
||||
const totalCommissions = await prisma.commission.count()
|
||||
|
||||
// Get amounts by status
|
||||
const [pendingResult, approvedResult, paidResult] = await Promise.all([
|
||||
prisma.commission.aggregate({
|
||||
where: { status: 'PENDING' },
|
||||
_sum: { amount: true }
|
||||
}),
|
||||
prisma.commission.aggregate({
|
||||
where: { status: 'APPROVED' },
|
||||
_sum: { amount: true }
|
||||
}),
|
||||
prisma.commission.aggregate({
|
||||
where: { status: 'PAID' },
|
||||
_sum: { amount: true }
|
||||
})
|
||||
])
|
||||
|
||||
// Get this month commissions
|
||||
const startOfMonth = new Date()
|
||||
startOfMonth.setDate(1)
|
||||
startOfMonth.setHours(0, 0, 0, 0)
|
||||
|
||||
const thisMonthCommissions = await prisma.commission.count({
|
||||
where: {
|
||||
createdAt: { gte: startOfMonth }
|
||||
}
|
||||
})
|
||||
|
||||
// Calculate average commission
|
||||
const totalAmount = (pendingResult._sum.amount || 0) + (approvedResult._sum.amount || 0) + (paidResult._sum.amount || 0)
|
||||
const averageCommission = totalCommissions > 0 ? totalAmount / totalCommissions : 0
|
||||
|
||||
return NextResponse.json({
|
||||
totalCommissions: totalCommissions || 0,
|
||||
pendingAmount: pendingResult._sum.amount || 0,
|
||||
approvedAmount: approvedResult._sum.amount || 0,
|
||||
paidAmount: paidResult._sum.amount || 0,
|
||||
thisMonthCommissions: thisMonthCommissions || 0,
|
||||
averageCommission: averageCommission || 0
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching commission stats:', error)
|
||||
return NextResponse.json({
|
||||
totalCommissions: 0,
|
||||
pendingAmount: 0,
|
||||
approvedAmount: 0,
|
||||
paidAmount: 0,
|
||||
thisMonthCommissions: 0,
|
||||
averageCommission: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
34
app/api/admin/dashboard/recent-orders/route.ts
Normal file
34
app/api/admin/dashboard/recent-orders/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const orders = await prisma.order.findMany({
|
||||
take: 10,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json({ orders })
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent orders:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch recent orders' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
128
app/api/admin/dashboard/stats/route.ts
Normal file
128
app/api/admin/dashboard/stats/route.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get current date ranges
|
||||
const now = new Date()
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const startOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
|
||||
const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0)
|
||||
|
||||
// Get current totals
|
||||
const [totalUsers, totalProducts, totalOrders, totalRevenueResult] = await Promise.all([
|
||||
prisma.user.count(),
|
||||
prisma.product.count({ where: { isActive: true } }),
|
||||
prisma.order.count(),
|
||||
prisma.order.aggregate({
|
||||
where: { status: { in: ['PAID', 'SHIPPED', 'DELIVERED'] } },
|
||||
_sum: { total: true }
|
||||
})
|
||||
])
|
||||
|
||||
// Get last month data for comparison
|
||||
const [lastMonthUsers, lastMonthProducts, lastMonthOrders, lastMonthRevenueResult] = await Promise.all([
|
||||
prisma.user.count({
|
||||
where: {
|
||||
joinedAt: {
|
||||
gte: startOfLastMonth,
|
||||
lte: endOfLastMonth
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.product.count({
|
||||
where: {
|
||||
isActive: true,
|
||||
createdAt: {
|
||||
gte: startOfLastMonth,
|
||||
lte: endOfLastMonth
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.order.count({
|
||||
where: {
|
||||
createdAt: {
|
||||
gte: startOfLastMonth,
|
||||
lte: endOfLastMonth
|
||||
}
|
||||
}
|
||||
}),
|
||||
prisma.order.aggregate({
|
||||
where: {
|
||||
status: { in: ['PAID', 'SHIPPED', 'DELIVERED'] },
|
||||
createdAt: {
|
||||
gte: startOfLastMonth,
|
||||
lte: endOfLastMonth
|
||||
}
|
||||
},
|
||||
_sum: { total: true }
|
||||
})
|
||||
])
|
||||
|
||||
// Get this month data
|
||||
const [thisMonthUsers, thisMonthProducts, thisMonthOrders, thisMonthRevenueResult] = await Promise.all([
|
||||
prisma.user.count({
|
||||
where: {
|
||||
joinedAt: { gte: startOfMonth }
|
||||
}
|
||||
}),
|
||||
prisma.product.count({
|
||||
where: {
|
||||
isActive: true,
|
||||
createdAt: { gte: startOfMonth }
|
||||
}
|
||||
}),
|
||||
prisma.order.count({
|
||||
where: {
|
||||
createdAt: { gte: startOfMonth }
|
||||
}
|
||||
}),
|
||||
prisma.order.aggregate({
|
||||
where: {
|
||||
status: { in: ['PAID', 'SHIPPED', 'DELIVERED'] },
|
||||
createdAt: { gte: startOfMonth }
|
||||
},
|
||||
_sum: { total: true }
|
||||
})
|
||||
])
|
||||
|
||||
// Calculate growth percentages
|
||||
const calculateGrowth = (current: number, previous: number) => {
|
||||
if (previous === 0) return current > 0 ? 100 : 0
|
||||
return ((current - previous) / previous) * 100
|
||||
}
|
||||
|
||||
const totalRevenue = totalRevenueResult._sum.total || 0
|
||||
const lastMonthRevenue = lastMonthRevenueResult._sum.total || 0
|
||||
const thisMonthRevenue = thisMonthRevenueResult._sum.total || 0
|
||||
|
||||
const userGrowth = calculateGrowth(thisMonthUsers, lastMonthUsers)
|
||||
const productGrowth = calculateGrowth(thisMonthProducts, lastMonthProducts)
|
||||
const orderGrowth = calculateGrowth(thisMonthOrders, lastMonthOrders)
|
||||
const revenueGrowth = calculateGrowth(thisMonthRevenue, lastMonthRevenue)
|
||||
|
||||
return NextResponse.json({
|
||||
totalUsers,
|
||||
totalProducts,
|
||||
totalOrders,
|
||||
totalRevenue,
|
||||
userGrowth,
|
||||
productGrowth,
|
||||
orderGrowth,
|
||||
revenueGrowth
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching dashboard stats:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch dashboard stats' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
59
app/api/admin/dashboard/top-products/route.ts
Normal file
59
app/api/admin/dashboard/top-products/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get top products by order count this month
|
||||
const startOfMonth = new Date()
|
||||
startOfMonth.setDate(1)
|
||||
startOfMonth.setHours(0, 0, 0, 0)
|
||||
|
||||
const topProducts = await prisma.product.findMany({
|
||||
take: 5,
|
||||
include: {
|
||||
orderItems: {
|
||||
where: {
|
||||
order: {
|
||||
createdAt: { gte: startOfMonth },
|
||||
status: { in: ['PAID', 'SHIPPED', 'DELIVERED'] }
|
||||
}
|
||||
},
|
||||
select: {
|
||||
quantity: true,
|
||||
price: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const productsWithStats = topProducts.map(product => {
|
||||
const orderCount = product.orderItems.reduce((sum, item) => sum + item.quantity, 0)
|
||||
const totalRevenue = product.orderItems.reduce((sum, item) => sum + (item.price * item.quantity), 0)
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.price,
|
||||
orderCount,
|
||||
totalRevenue
|
||||
}
|
||||
}).filter(product => product.orderCount > 0)
|
||||
.sort((a, b) => b.orderCount - a.orderCount)
|
||||
.slice(0, 5)
|
||||
|
||||
return NextResponse.json({ products: productsWithStats })
|
||||
} catch (error) {
|
||||
console.error('Error fetching top products:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch top products' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
148
app/api/admin/orders/[id]/route.ts
Normal file
148
app/api/admin/orders/[id]/route.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
phone: true
|
||||
}
|
||||
},
|
||||
orderItems: {
|
||||
include: {
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
sku: true,
|
||||
price: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
shippingAddress: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
company: true,
|
||||
address1: true,
|
||||
address2: true,
|
||||
city: true,
|
||||
state: true,
|
||||
zipCode: true,
|
||||
country: true,
|
||||
phone: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
return NextResponse.json({ error: 'Order not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json(order)
|
||||
} catch (error) {
|
||||
console.error('Error fetching order:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch order' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const body = await request.json()
|
||||
const { status } = body
|
||||
|
||||
if (!['PENDING', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED'].includes(status)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid status' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const order = await prisma.order.update({
|
||||
where: { id },
|
||||
data: {
|
||||
status,
|
||||
updatedAt: new Date()
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
phone: true
|
||||
}
|
||||
},
|
||||
orderItems: {
|
||||
include: {
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true,
|
||||
sku: true,
|
||||
price: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
shippingAddress: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
company: true,
|
||||
address1: true,
|
||||
address2: true,
|
||||
city: true,
|
||||
state: true,
|
||||
zipCode: true,
|
||||
country: true,
|
||||
phone: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(order)
|
||||
} catch (error) {
|
||||
console.error('Error updating order:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update order' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
48
app/api/admin/orders/bulk/route.ts
Normal file
48
app/api/admin/orders/bulk/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { orderIds, status } = body
|
||||
|
||||
if (!orderIds || !Array.isArray(orderIds) || orderIds.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Order IDs are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
if (!['PENDING', 'PAID', 'SHIPPED', 'DELIVERED', 'CANCELLED'].includes(status)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid status' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
await prisma.order.updateMany({
|
||||
where: {
|
||||
id: { in: orderIds }
|
||||
},
|
||||
data: {
|
||||
status,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Error in bulk order update:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to perform bulk update' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
72
app/api/admin/orders/route.ts
Normal file
72
app/api/admin/orders/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const search = searchParams.get('search')
|
||||
const status = searchParams.get('status')
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const where: any = {}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ id: { contains: search, mode: 'insensitive' } },
|
||||
{ razorpayOrderId: { contains: search, mode: 'insensitive' } },
|
||||
{ user: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ user: { email: { contains: search, mode: 'insensitive' } } }
|
||||
]
|
||||
}
|
||||
|
||||
if (status) {
|
||||
where.status = status
|
||||
}
|
||||
|
||||
const orders = await prisma.order.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
},
|
||||
orderItems: {
|
||||
include: {
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
images: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
skip,
|
||||
take: limit
|
||||
})
|
||||
|
||||
return NextResponse.json(orders)
|
||||
} catch (error) {
|
||||
console.error('Error fetching orders:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch orders' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
66
app/api/admin/payouts/[id]/route.ts
Normal file
66
app/api/admin/payouts/[id]/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id: payoutId } = await params
|
||||
const { status, adminNotes } = await request.json()
|
||||
|
||||
// Validate status
|
||||
if (!['APPROVED', 'REJECTED', 'PAID'].includes(status)) {
|
||||
return NextResponse.json({ error: 'Invalid status' }, { status: 400 })
|
||||
}
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
// Get the payout
|
||||
const payout = await tx.payout.findUnique({
|
||||
where: { id: payoutId },
|
||||
include: { user: true }
|
||||
})
|
||||
|
||||
if (!payout) {
|
||||
throw new Error('Payout not found')
|
||||
}
|
||||
|
||||
// Update payout
|
||||
const updatedPayout = await tx.payout.update({
|
||||
where: { id: payoutId },
|
||||
data: {
|
||||
status,
|
||||
adminNotes,
|
||||
updatedAt: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
// If rejecting, restore the amount to user's wallet
|
||||
if (status === 'REJECTED') {
|
||||
await tx.wallet.update({
|
||||
where: { userId: payout.userId },
|
||||
data: {
|
||||
balance: { increment: payout.amount }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return updatedPayout
|
||||
})
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error) {
|
||||
console.error('Error updating payout:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to update payout' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
59
app/api/admin/payouts/route.ts
Normal file
59
app/api/admin/payouts/route.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const url = new URL(request.url)
|
||||
const status = url.searchParams.get('status')
|
||||
const page = parseInt(url.searchParams.get('page') || '1')
|
||||
const limit = parseInt(url.searchParams.get('limit') || '50')
|
||||
|
||||
const where: any = {}
|
||||
if (status && status !== 'all') {
|
||||
where.status = status
|
||||
}
|
||||
|
||||
const payouts = await prisma.payout.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
skip: (page - 1) * limit,
|
||||
take: limit
|
||||
})
|
||||
|
||||
const total = await prisma.payout.count({ where })
|
||||
|
||||
return NextResponse.json({
|
||||
payouts,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching admin payouts:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch payouts' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
45
app/api/admin/payouts/stats/route.ts
Normal file
45
app/api/admin/payouts/stats/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get total requests
|
||||
const totalRequests = await prisma.payout.count()
|
||||
|
||||
// Get amounts by status
|
||||
const [pendingResult, approvedResult, paidResult] = await Promise.all([
|
||||
prisma.payout.aggregate({
|
||||
where: { status: 'PENDING' },
|
||||
_sum: { amount: true }
|
||||
}),
|
||||
prisma.payout.aggregate({
|
||||
where: { status: 'APPROVED' },
|
||||
_sum: { amount: true }
|
||||
}),
|
||||
prisma.payout.aggregate({
|
||||
where: { status: 'PAID' },
|
||||
_sum: { amount: true }
|
||||
})
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
totalRequests,
|
||||
pendingAmount: pendingResult._sum.amount || 0,
|
||||
approvedAmount: approvedResult._sum.amount || 0,
|
||||
paidAmount: paidResult._sum.amount || 0
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching payout stats:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch payout stats' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
114
app/api/admin/products/[id]/route.ts
Normal file
114
app/api/admin/products/[id]/route.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const product = await prisma.product.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
category: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return NextResponse.json({ error: 'Product not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json(product)
|
||||
} catch (error) {
|
||||
console.error('Error fetching product:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch product' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const body = await request.json()
|
||||
const { name, description, price, discount, images, stock, sku, isActive, categoryId, weight } = body
|
||||
|
||||
const product = await prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
discount,
|
||||
images,
|
||||
stock,
|
||||
sku,
|
||||
isActive,
|
||||
categoryId,
|
||||
weight,
|
||||
slug: name.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-') + '-' + Date.now()
|
||||
},
|
||||
include: {
|
||||
category: true
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(product)
|
||||
} catch (error) {
|
||||
console.error('Error updating product:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update product' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
await prisma.product.delete({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Error deleting product:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete product' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
61
app/api/admin/products/bulk/route.ts
Normal file
61
app/api/admin/products/bulk/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { productIds, action } = body
|
||||
|
||||
if (!productIds || !Array.isArray(productIds) || productIds.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Product IDs are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let updateData: any = {}
|
||||
|
||||
switch (action) {
|
||||
case 'activate':
|
||||
updateData = { isActive: true }
|
||||
break
|
||||
case 'deactivate':
|
||||
updateData = { isActive: false }
|
||||
break
|
||||
case 'delete':
|
||||
await prisma.product.deleteMany({
|
||||
where: {
|
||||
id: { in: productIds }
|
||||
}
|
||||
})
|
||||
return NextResponse.json({ success: true })
|
||||
default:
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid action' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
await prisma.product.updateMany({
|
||||
where: {
|
||||
id: { in: productIds }
|
||||
},
|
||||
data: updateData
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Error in bulk action:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to perform bulk action' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
195
app/api/admin/products/export/route.ts
Normal file
195
app/api/admin/products/export/route.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const selectedColumns = searchParams.get('columns')?.split(',') || []
|
||||
const categoryId = searchParams.get('categoryId') || ''
|
||||
const isActive = searchParams.get('isActive') || ''
|
||||
const limit = parseInt(searchParams.get('limit') || '10000')
|
||||
|
||||
// Build where clause based on filters
|
||||
const whereClause: any = {}
|
||||
|
||||
if (categoryId && categoryId !== '') {
|
||||
whereClause.categoryId = categoryId
|
||||
}
|
||||
|
||||
if (isActive && isActive !== '') {
|
||||
whereClause.isActive = isActive === 'true'
|
||||
}
|
||||
|
||||
// Fetch products with category information
|
||||
const products = await prisma.product.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
category: {
|
||||
select: { name: true }
|
||||
}
|
||||
},
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
|
||||
// Transform data based on selected columns
|
||||
const transformedData = products.map(product => {
|
||||
const baseData = {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
description: product.description || '',
|
||||
price: product.price,
|
||||
discount: product.discount,
|
||||
stock: product.stock,
|
||||
sku: product.sku,
|
||||
slug: product.slug,
|
||||
isActive: product.isActive,
|
||||
categoryName: product.category?.name || '',
|
||||
categoryId: product.categoryId || '',
|
||||
brand: product.brand || '',
|
||||
origin: product.origin || '',
|
||||
weight: product.weight || '',
|
||||
images: product.images.join(', '),
|
||||
createdAt: product.createdAt.toISOString(),
|
||||
updatedAt: product.updatedAt.toISOString(),
|
||||
// Calculated fields
|
||||
finalPrice: product.price - (product.price * product.discount / 100),
|
||||
discountAmount: product.price * product.discount / 100,
|
||||
stockStatus: product.stock > 10 ? 'In Stock' : product.stock > 0 ? 'Low Stock' : 'Out of Stock',
|
||||
status: product.isActive ? 'Active' : 'Inactive'
|
||||
}
|
||||
|
||||
// Filter by selected columns if specified
|
||||
if (selectedColumns.length > 0) {
|
||||
const filteredData: any = {}
|
||||
selectedColumns.forEach(column => {
|
||||
if (column in baseData) {
|
||||
filteredData[column] = (baseData as any)[column]
|
||||
}
|
||||
})
|
||||
return filteredData
|
||||
}
|
||||
|
||||
return baseData
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: transformedData,
|
||||
count: transformedData.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Product export error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { columns = [], filters = {} } = await request.json()
|
||||
const selectedColumns = columns || []
|
||||
const categoryId = filters.categoryId || ''
|
||||
const isActive = filters.isActive || ''
|
||||
const limit = parseInt(filters.limit || '10000')
|
||||
|
||||
// Build where clause based on filters
|
||||
const whereClause: any = {}
|
||||
|
||||
if (categoryId && categoryId !== '') {
|
||||
whereClause.categoryId = categoryId
|
||||
}
|
||||
|
||||
if (isActive && isActive !== '') {
|
||||
whereClause.isActive = isActive === 'true'
|
||||
}
|
||||
|
||||
// Fetch products with category information
|
||||
const products = await prisma.product.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
category: {
|
||||
select: { name: true }
|
||||
}
|
||||
},
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' }
|
||||
})
|
||||
|
||||
// Transform data based on selected columns
|
||||
const transformedData = products.map(product => {
|
||||
const baseData = {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
description: product.description || '',
|
||||
price: product.price,
|
||||
discount: product.discount,
|
||||
stock: product.stock,
|
||||
sku: product.sku,
|
||||
slug: product.slug,
|
||||
isActive: product.isActive,
|
||||
categoryName: product.category?.name || '',
|
||||
categoryId: product.categoryId || '',
|
||||
brand: product.brand || '',
|
||||
origin: product.origin || '',
|
||||
weight: product.weight || '',
|
||||
images: product.images.join(', '),
|
||||
createdAt: product.createdAt.toISOString(),
|
||||
updatedAt: product.updatedAt.toISOString(),
|
||||
// Calculated fields
|
||||
finalPrice: product.price - (product.price * product.discount / 100),
|
||||
discountAmount: product.price * product.discount / 100,
|
||||
stockStatus: product.stock > 10 ? 'In Stock' : product.stock > 0 ? 'Low Stock' : 'Out of Stock',
|
||||
status: product.isActive ? 'Active' : 'Inactive'
|
||||
}
|
||||
|
||||
// Filter by selected columns if specified
|
||||
if (selectedColumns.length > 0) {
|
||||
const filteredData: any = {}
|
||||
selectedColumns.forEach((column: string) => {
|
||||
if (column in baseData) {
|
||||
filteredData[column] = (baseData as any)[column]
|
||||
}
|
||||
})
|
||||
return filteredData
|
||||
}
|
||||
|
||||
return baseData
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: transformedData,
|
||||
count: transformedData.length
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Product export error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
162
app/api/admin/products/import/route.ts
Normal file
162
app/api/admin/products/import/route.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Unauthorized' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { data } = await request.json()
|
||||
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'No products provided' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
let successCount = 0
|
||||
let errorCount = 0
|
||||
const errors: string[] = []
|
||||
|
||||
// Process products in batches to avoid overwhelming the database
|
||||
const batchSize = 50
|
||||
const batches: any[][] = []
|
||||
for (let i = 0; i < data.length; i += batchSize) {
|
||||
batches.push(data.slice(i, i + batchSize))
|
||||
}
|
||||
|
||||
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
||||
const batch = batches[batchIndex]
|
||||
try {
|
||||
const productsToCreate: any[] = []
|
||||
const productsToUpdate: any[] = []
|
||||
|
||||
for (let index = 0; index < batch.length; index++) {
|
||||
const productData = batch[index]
|
||||
try {
|
||||
// Validate required fields
|
||||
if (!productData.name || !productData.price) {
|
||||
errors.push(`Row ${batchIndex * batchSize + index + 1}: Missing required fields (name, price)`)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if category exists
|
||||
let categoryId: string | undefined = undefined
|
||||
if (productData.categoryId) {
|
||||
const category = await prisma.category.findUnique({
|
||||
where: { id: productData.categoryId }
|
||||
})
|
||||
if (category) {
|
||||
categoryId = category.id
|
||||
} else {
|
||||
errors.push(`Row ${batchIndex * batchSize + index + 1}: Category with ID '${productData.categoryId}' not found`)
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SKU if not provided
|
||||
const sku = productData.sku || `PROD-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
// Prepare product data
|
||||
const productPayload = {
|
||||
name: productData.name.trim(),
|
||||
description: productData.description || '',
|
||||
price: parseFloat(productData.price) || 0,
|
||||
discount: parseFloat(productData.discount) || 0,
|
||||
stock: parseInt(productData.stock) || 0,
|
||||
sku: sku,
|
||||
slug: productData.slug || productData.name.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
|
||||
isActive: productData.active === 'true' || productData.active === true || productData.active === '1' || true,
|
||||
...(categoryId && { categoryId }),
|
||||
brand: productData.brand || '',
|
||||
origin: productData.origin || '',
|
||||
weight: productData.weight || '',
|
||||
images: productData.images ? (typeof productData.images === 'string' ? productData.images.split(',').map((img: string) => img.trim()) : []) : []
|
||||
}
|
||||
|
||||
// Check if product exists (by SKU)
|
||||
const existingProduct = await prisma.product.findUnique({
|
||||
where: { sku: productPayload.sku }
|
||||
})
|
||||
|
||||
if (existingProduct) {
|
||||
// Update existing product - create update data without null categoryId
|
||||
const updatePayload = { ...productPayload }
|
||||
if (!categoryId) {
|
||||
delete updatePayload.categoryId
|
||||
}
|
||||
|
||||
productsToUpdate.push({
|
||||
where: { id: existingProduct.id },
|
||||
data: updatePayload
|
||||
})
|
||||
} else {
|
||||
// Create new product
|
||||
productsToCreate.push(productPayload)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
errors.push(`Row ${batchIndex * batchSize + index + 1}: ${error instanceof Error ? error.message : 'Processing error'}`)
|
||||
errorCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Create new products
|
||||
if (productsToCreate.length > 0) {
|
||||
try {
|
||||
await prisma.product.createMany({
|
||||
data: productsToCreate,
|
||||
skipDuplicates: true
|
||||
})
|
||||
successCount += productsToCreate.length
|
||||
} catch (error) {
|
||||
errors.push(`Batch ${batchIndex + 1}: Failed to create products - ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
errorCount += productsToCreate.length
|
||||
}
|
||||
}
|
||||
|
||||
// Update existing products
|
||||
for (const updateData of productsToUpdate) {
|
||||
try {
|
||||
await prisma.product.update(updateData)
|
||||
successCount++
|
||||
} catch (error) {
|
||||
errors.push(`Failed to update product: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
||||
errorCount++
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
errors.push(`Batch ${batchIndex + 1}: ${error instanceof Error ? error.message : 'Batch processing error'}`)
|
||||
errorCount += batch.length
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: errorCount === 0,
|
||||
message: errorCount === 0 ?
|
||||
`Successfully imported ${successCount} products` :
|
||||
`Import completed with errors. ${successCount} successful, ${errorCount} failed.`,
|
||||
successCount,
|
||||
errorCount,
|
||||
errors: errors.slice(0, 50) // Limit to first 50 errors
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('Product import error:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, message: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
54
app/api/admin/products/route.ts
Normal file
54
app/api/admin/products/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
const { name, description, price, discount, images, stock, sku, isActive, categoryId } = body
|
||||
|
||||
// Check if SKU already exists
|
||||
const existingSku = await prisma.product.findUnique({
|
||||
where: { sku }
|
||||
})
|
||||
|
||||
if (existingSku) {
|
||||
return NextResponse.json(
|
||||
{ error: 'SKU already exists' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const product = await prisma.product.create({
|
||||
data: {
|
||||
name,
|
||||
description,
|
||||
price,
|
||||
discount,
|
||||
images,
|
||||
stock,
|
||||
sku,
|
||||
isActive,
|
||||
categoryId,
|
||||
slug: name.toLowerCase().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-') + '-' + Date.now()
|
||||
},
|
||||
include: {
|
||||
category: true
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(product)
|
||||
} catch (error) {
|
||||
console.error('Error creating product:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create product' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
35
app/api/admin/reviews/[id]/approve/route.ts
Normal file
35
app/api/admin/reviews/[id]/approve/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: Props
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const review = await prisma.review.update({
|
||||
where: { id },
|
||||
data: { isApproved: true }
|
||||
})
|
||||
|
||||
return NextResponse.json({ review })
|
||||
} catch (error) {
|
||||
console.error('Error approving review:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to approve review' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
35
app/api/admin/reviews/[id]/reject/route.ts
Normal file
35
app/api/admin/reviews/[id]/reject/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: Props
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
const review = await prisma.review.update({
|
||||
where: { id },
|
||||
data: { isApproved: false }
|
||||
})
|
||||
|
||||
return NextResponse.json({ review })
|
||||
} catch (error) {
|
||||
console.error('Error rejecting review:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to reject review' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
44
app/api/admin/reviews/[id]/route.ts
Normal file
44
app/api/admin/reviews/[id]/route.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ id: string }>
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: Props
|
||||
) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
|
||||
// Delete related records first
|
||||
await prisma.reviewHelpfulVote.deleteMany({
|
||||
where: { reviewId: id }
|
||||
})
|
||||
|
||||
await prisma.reviewReport.deleteMany({
|
||||
where: { reviewId: id }
|
||||
})
|
||||
|
||||
// Delete the review
|
||||
await prisma.review.delete({
|
||||
where: { id }
|
||||
})
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
} catch (error) {
|
||||
console.error('Error deleting review:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete review' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
91
app/api/admin/reviews/route.ts
Normal file
91
app/api/admin/reviews/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
const search = searchParams.get('search') || ''
|
||||
const approved = searchParams.get('approved')
|
||||
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const where: any = {}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ title: { contains: search, mode: 'insensitive' } },
|
||||
{ comment: { contains: search, mode: 'insensitive' } },
|
||||
{ user: { name: { contains: search, mode: 'insensitive' } } },
|
||||
{ product: { name: { contains: search, mode: 'insensitive' } } }
|
||||
]
|
||||
}
|
||||
|
||||
if (approved !== null && approved !== undefined) {
|
||||
where.isApproved = approved === 'true'
|
||||
}
|
||||
|
||||
const [reviews, total] = await Promise.all([
|
||||
prisma.review.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true
|
||||
}
|
||||
},
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true
|
||||
}
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
helpfulVotedBy: true,
|
||||
reportedBy: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc'
|
||||
},
|
||||
skip,
|
||||
take: limit
|
||||
}),
|
||||
prisma.review.count({ where })
|
||||
])
|
||||
|
||||
const reviewsWithCounts = reviews.map(review => ({
|
||||
...review,
|
||||
helpfulVotes: review._count.helpfulVotedBy,
|
||||
reportCount: review._count.reportedBy
|
||||
}))
|
||||
|
||||
return NextResponse.json({
|
||||
reviews: reviewsWithCounts,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching admin reviews:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch reviews' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
62
app/api/admin/settings/categories/route.ts
Normal file
62
app/api/admin/settings/categories/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const categories = await prisma.category.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
products: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: { name: 'asc' }
|
||||
})
|
||||
|
||||
const categoriesWithCount = categories.map(category => ({
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
description: category.description,
|
||||
image: category.image,
|
||||
isActive: category.isActive,
|
||||
productCount: category._count.products
|
||||
}))
|
||||
|
||||
return NextResponse.json({ categories: categoriesWithCount })
|
||||
} catch (error) {
|
||||
console.error('Error fetching categories:', error)
|
||||
return NextResponse.json({ categories: [] })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { name, description, image, isActive } = await request.json()
|
||||
|
||||
const category = await prisma.category.create({
|
||||
data: { name, description, image, isActive }
|
||||
})
|
||||
|
||||
return NextResponse.json(category)
|
||||
} catch (error) {
|
||||
console.error('Error creating category:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create category' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
46
app/api/admin/settings/commissions/route.ts
Normal file
46
app/api/admin/settings/commissions/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const settings = await prisma.commissionSettings.findMany({
|
||||
orderBy: { level: 'asc' }
|
||||
})
|
||||
|
||||
return NextResponse.json({ settings })
|
||||
} catch (error) {
|
||||
console.error('Error fetching commission settings:', error)
|
||||
return NextResponse.json({ settings: [] })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { level, percentage, isActive } = await request.json()
|
||||
|
||||
const setting = await prisma.commissionSettings.create({
|
||||
data: { level, percentage, isActive }
|
||||
})
|
||||
|
||||
return NextResponse.json(setting)
|
||||
} catch (error) {
|
||||
console.error('Error creating commission setting:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to create commission setting' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
128
app/api/admin/settings/system/route.ts
Normal file
128
app/api/admin/settings/system/route.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { clearSettingsCache } from '@/lib/settings'
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get or create system settings
|
||||
let settings = await prisma.systemSettings.findFirst()
|
||||
|
||||
if (!settings) {
|
||||
// Create default settings if none exist
|
||||
settings = await prisma.systemSettings.create({
|
||||
data: {
|
||||
siteName: 'Padmaaja Rasooi',
|
||||
siteDescription: 'Premium Rice Products & Quality Grains',
|
||||
supportEmail: 'support@padmaajarasooi.com',
|
||||
minimumPayout: 100,
|
||||
enableReferrals: true,
|
||||
enableCommissions: true,
|
||||
maintenanceMode: false,
|
||||
allowRegistration: true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
siteName: settings.siteName,
|
||||
siteDescription: settings.siteDescription,
|
||||
supportEmail: settings.supportEmail,
|
||||
minimumPayout: settings.minimumPayout,
|
||||
enableReferrals: settings.enableReferrals,
|
||||
enableCommissions: settings.enableCommissions,
|
||||
maintenanceMode: settings.maintenanceMode,
|
||||
allowRegistration: settings.allowRegistration
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching system settings:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch system settings' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user?.id || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const {
|
||||
siteName,
|
||||
siteDescription,
|
||||
supportEmail,
|
||||
minimumPayout,
|
||||
enableReferrals,
|
||||
enableCommissions,
|
||||
maintenanceMode,
|
||||
allowRegistration
|
||||
} = await request.json()
|
||||
|
||||
// Get or create system settings
|
||||
let settings = await prisma.systemSettings.findFirst()
|
||||
|
||||
if (!settings) {
|
||||
// Create new settings
|
||||
settings = await prisma.systemSettings.create({
|
||||
data: {
|
||||
siteName,
|
||||
siteDescription,
|
||||
supportEmail,
|
||||
minimumPayout,
|
||||
enableReferrals,
|
||||
enableCommissions,
|
||||
maintenanceMode,
|
||||
allowRegistration
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Update existing settings
|
||||
settings = await prisma.systemSettings.update({
|
||||
where: { id: settings.id },
|
||||
data: {
|
||||
siteName,
|
||||
siteDescription,
|
||||
supportEmail,
|
||||
minimumPayout,
|
||||
enableReferrals,
|
||||
enableCommissions,
|
||||
maintenanceMode,
|
||||
allowRegistration
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clear the settings cache so new values are fetched
|
||||
clearSettingsCache()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
settings: {
|
||||
siteName: settings.siteName,
|
||||
siteDescription: settings.siteDescription,
|
||||
supportEmail: settings.supportEmail,
|
||||
minimumPayout: settings.minimumPayout,
|
||||
enableReferrals: settings.enableReferrals,
|
||||
enableCommissions: settings.enableCommissions,
|
||||
maintenanceMode: settings.maintenanceMode,
|
||||
allowRegistration: settings.allowRegistration
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error updating system settings:', error)
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update system settings' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
76
app/api/admin/users/route.ts
Normal file
76
app/api/admin/users/route.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from '@/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
if (!session?.user || session.user.role !== 'ADMIN') {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '10')
|
||||
const search = searchParams.get('search')
|
||||
const role = searchParams.get('role')
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
const where: any = {}
|
||||
|
||||
if (search) {
|
||||
where.OR = [
|
||||
{ name: { contains: search, mode: 'insensitive' } },
|
||||
{ email: { contains: search, mode: 'insensitive' } },
|
||||
]
|
||||
}
|
||||
|
||||
if (role) {
|
||||
where.role = role
|
||||
}
|
||||
|
||||
const [users, total] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
role: true,
|
||||
isActive: true,
|
||||
joinedAt: true,
|
||||
referralCode: true,
|
||||
referrer: {
|
||||
select: {
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
_count: {
|
||||
select: {
|
||||
referrals: true,
|
||||
orders: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { joinedAt: 'desc' },
|
||||
}),
|
||||
prisma.user.count({ where }),
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
users,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
pages: Math.ceil(total / limit),
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin users API error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user