init project

This commit is contained in:
@kirill.komarov
2026-04-28 11:02:08 +05:00
commit 55480d4aa5
50 changed files with 9241 additions and 0 deletions
@@ -0,0 +1,74 @@
import type { Category, Product } from '@/entities/product/model/types'
import { apiClient } from '@/shared/api/client'
export async function fetchPublicProducts(categorySlug?: string): Promise<Product[]> {
const { data } = await apiClient.get<Product[]>('products', {
params: categorySlug ? { categorySlug } : undefined,
})
return data
}
export async function fetchCategories(): Promise<Category[]> {
const { data } = await apiClient.get<Category[]>('categories')
return data
}
export async function fetchAdminProducts(token: string): Promise<Product[]> {
const { data } = await apiClient.get<Product[]>('admin/products', {
headers: { Authorization: `Bearer ${token}` },
})
return data
}
export async function createProduct(
token: string,
body: {
title: string
slug?: string
description?: string | null
priceCents: number
imageUrl?: string | null
published: boolean
categoryId: string
},
): Promise<Product> {
const { data } = await apiClient.post<Product>('admin/products', body, {
headers: { Authorization: `Bearer ${token}` },
})
return data
}
export async function updateProduct(
token: string,
id: string,
body: Partial<{
title: string
slug: string
description: string | null
priceCents: number
imageUrl: string | null
published: boolean
categoryId: string
}>,
): Promise<Product> {
const { data } = await apiClient.patch<Product>(`admin/products/${id}`, body, {
headers: { Authorization: `Bearer ${token}` },
})
return data
}
export async function deleteProduct(token: string, id: string): Promise<void> {
await apiClient.delete(`admin/products/${id}`, {
headers: { Authorization: `Bearer ${token}` },
})
}
export async function createCategory(
token: string,
body: { name: string; slug?: string; sort?: number },
): Promise<Category> {
const { data } = await apiClient.post<Category>('admin/categories', body, {
headers: { Authorization: `Bearer ${token}` },
})
return data
}
@@ -0,0 +1,20 @@
export type Category = {
id: string
name: string
slug: string
sort: number
}
export type Product = {
id: string
title: string
slug: string
description: string | null
priceCents: number
imageUrl: string | null
published: boolean
categoryId: string
createdAt: string
updatedAt: string
category?: Category
}
@@ -0,0 +1,63 @@
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import CardMedia from '@mui/material/CardMedia'
import Chip from '@mui/material/Chip'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import type { Product } from '@/entities/product/model/types'
import { formatPriceRub } from '@/shared/lib/format-price'
type Props = { product: Product }
export function ProductCard({ product }: Props) {
return (
<Card variant="outlined" sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
{product.imageUrl ? (
<CardMedia
component="img"
height="200"
image={product.imageUrl}
alt={product.title}
sx={{ objectFit: 'cover' }}
/>
) : (
<CardMedia
component="div"
sx={{
height: 200,
bgcolor: 'grey.100',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography color="text.secondary">Нет фото</Typography>
</CardMedia>
)}
<CardContent sx={{ flexGrow: 1 }}>
<Stack spacing={1}>
{product.category && <Chip label={product.category.name} size="small" />}
<Typography variant="h6" component="h2">
{product.title}
</Typography>
<Typography
variant="body2"
color="text.secondary"
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
WebkitLineClamp: 3,
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
}}
>
{product.description ?? 'Описание появится позже.'}
</Typography>
<Typography variant="h6" color="primary">
{formatPriceRub(product.priceCents)}
</Typography>
</Stack>
</CardContent>
</Card>
)
}