Init(Core): add to repo and add seeders

This commit is contained in:
2026-04-28 22:48:42 +03:30
commit be6b699ff0
205 changed files with 22524 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
export default function ApplicationLogo(props) {
return (
<svg {...props} viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg">
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z" />
</svg>
);
}

View File

@@ -0,0 +1,5 @@
export default function Button({ onClick, children, className, disabled }) {
return (
<button disabled={ disabled } onClick={ onClick } className={"rounded-lg shadow-sm children-white p-2 active:outline active:animate-pulse " + className}>{children}</button>
);
}

View File

@@ -0,0 +1,10 @@
export default function CategoryAddItemButton({ onClick }){
return (
<button className="flex flex-col items-center h-full bg-gray-200 p-6 rounded-lg">
<span className="text-lg">افزودن محصول</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
</button>
)
}

View File

@@ -0,0 +1,22 @@
import CategoryItemCard from "@/Components/CategoryItemCard"
export default function CategoryCard({ category }){
return (
<div className="flex bg-white rounded-lg col-span-2 lg:col-span-1 h-full p-6 justify-between">
<div className="flex flex-col">
<span className="text-xl font-semibold">{category.title}</span>
<span className="text-gray-600">{category.products.length } محصول</span>
<span className="text-gray-600">{category.description}</span>
</div>
<div className="flex">
{category.products[0] ? <CategoryItemCard item={ category.products[0] } /> : null}
<button className="flex flex-col items-center h-full bg-gray-200 p-6 rounded-lg z-40">
<span className="text-lg">افزودن محصول</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
</button>
</div>
</div>
)
}

View File

@@ -0,0 +1,38 @@
import Input from '@/Components/Input'
import TextArea from '@/Components/TextArea'
import Button from '@/Components/Button'
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function CategoryForm({ onCancel, onSubmit }) {
let [title, setTitle] = useState();
let [description, setDescription] = useState();
let [step, setStep] = useState(1);
function handleSubmit(){
let data = {
title: title,
description: description,
}
router.post('/categories/store', data);
onCancel();
}
if(step == 1){
return (
<div className="bg-white rounded-lg w-full max-w-lg h-fit p-8 space-y-4">
<span className="pb-4 text-lg font-semibold">دسته بندی جدید</span>
<Input label="عنوان" className="w-full ml-6" onChange={(e) => {setTitle(e.target.value)}} />
<TextArea label="توضیحات (اختیاری)" className="" onChange={(e) => {setDescription(e.target.value)}}/>
<div className="flex">
<Button onClick={ onCancel } className="bg-white text-black border-gray-300 border-2 w-full transition hover:bg-zinc-100 ml-4 shadow-none">لغو</Button>
<Button onClick={ e => setStep(2) } className="w-full text-white bg-zinc-800 transition hover:bg-zinc-700">ایجاد</Button>
</div>
</div>
);
}
return("step2");
}

View File

@@ -0,0 +1,9 @@
export default function CategoryCard({ item, h }){
console.log(item);
return (
<div className={"ml-[-52px] w-32 h-full first:scale-[90%] z-30 my-auto bg-red-300 rounded-lg transition duration-600 hover:z-50 hover:scale-100"}>
{item.title}
<img src={item.images[0] ? item.images[0].thumbnail : null} alt={item.images[0] ? item.images[0].alt : null} />
</div>
)
}

View File

@@ -0,0 +1,12 @@
export default function Checkbox({ className = '', ...props }) {
return (
<input
{...props}
type="checkbox"
className={
'rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500 ' +
className
}
/>
);
}

View File

@@ -0,0 +1,11 @@
export default function CreateButton({ onClick, title }){
return (
<button onClick={ onClick } className="bg-zinc-900 text-white rounded-lg w-fit h-full py-2 px-6 flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
<span className="pr-4 leading-none">{ title }</span>
</button>
)
}

View File

@@ -0,0 +1,15 @@
export default function DangerButton({ className = '', disabled, children, ...props }) {
return (
<button
{...props}
className={
`inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150 ${
disabled && 'opacity-25'
} ` + className
}
disabled={disabled}
>
{children}
</button>
);
}

View File

@@ -0,0 +1,91 @@
import { useState, createContext, useContext, Fragment } from 'react';
import { Link } from '@inertiajs/react';
import { Transition } from '@headlessui/react';
const DropDownContext = createContext();
const Dropdown = ({ children }) => {
const [open, setOpen] = useState(false);
const toggleOpen = () => {
setOpen((previousState) => !previousState);
};
return (
<DropDownContext.Provider value={{ open, setOpen, toggleOpen }}>
<div className="relative">{children}</div>
</DropDownContext.Provider>
);
};
const Trigger = ({ children }) => {
const { open, setOpen, toggleOpen } = useContext(DropDownContext);
return (
<>
<div onClick={toggleOpen}>{children}</div>
{open && <div className="fixed inset-0 z-40" onClick={() => setOpen(false)}></div>}
</>
);
};
const Content = ({ align = 'right', width = '48', contentClasses = 'py-1 bg-white', children }) => {
const { open, setOpen } = useContext(DropDownContext);
let alignmentClasses = 'origin-top';
if (align === 'left') {
alignmentClasses = 'origin-top-left left-0';
} else if (align === 'right') {
alignmentClasses = 'origin-top-right right-0';
}
let widthClasses = '';
if (width === '48') {
widthClasses = 'w-48';
}
return (
<>
<Transition
as={Fragment}
show={open}
enter="transition ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div
className={`absolute z-50 mt-2 rounded-md shadow-lg ${alignmentClasses} ${widthClasses}`}
onClick={() => setOpen(false)}
>
<div className={`rounded-md ring-1 ring-black ring-opacity-5 ` + contentClasses}>{children}</div>
</div>
</Transition>
</>
);
};
const DropdownLink = ({ className = '', children, ...props }) => {
return (
<Link
{...props}
className={
'block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out ' +
className
}
>
{children}
</Link>
);
};
Dropdown.Trigger = Trigger;
Dropdown.Content = Content;
Dropdown.Link = DropdownLink;
export default Dropdown;

View File

@@ -0,0 +1,7 @@
export default function Header(){
return (
<div className="bg-white h-16 w-full rounded-lg">
Hello
</div>
)
}

View File

@@ -0,0 +1,22 @@
export default function TextArea({ image, onChange, id, label, className, placeholder=null }) {
return (
<div className={"flex flex-col h-full" + " " + className}>
<label className="pr-3 pb-0.5">{ label }</label>
<div class="flex items-center justify-center w-full h-full">
<label for="dropzone-file" class="flex flex-col items-center justify-center w-full h-full border border-gray-300 rounded-lg cursor-pointer">
<div class="w-full h-full flex flex-col items-center justify-center p-6">
{ image ? image : (
<div className="flex justify-center items-center bg-gray-200 rounded-lg w-full h-52">
<svg class="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
</svg>
</div>
)}
<span className="pt-4">برای آپلود عکس کلیک کنید</span>
</div>
<input onChange={ onChange } id="dropzone-file" type="file" class="hidden" />
</label>
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
export default function Input({ error, onChange, id, label, className, placeholder=null, value }) {
return (
<div className={"flex flex-col" + " " + className}>
<label className="pr-3 pb-0.5">{ label }</label>
<input onChange={ onChange } id={id} type="text" className="w-full border-gray-300 rounded-lg" placeholder={placeholder} value={value}/>
{error && <span className="text-sm text-red-600 pt-0.5 pr-3">{error}</span>}
</div>
);
}

View File

@@ -0,0 +1,7 @@
export default function InputError({ message, className = '', ...props }) {
return message ? (
<p {...props} className={'text-sm text-red-600 ' + className}>
{message}
</p>
) : null;
}

View File

@@ -0,0 +1,7 @@
export default function InputLabel({ value, className = '', children, ...props }) {
return (
<label {...props} className={`block font-medium text-sm text-gray-700 ` + className}>
{value ? value : children}
</label>
);
}

View File

@@ -0,0 +1,57 @@
import { Fragment } from 'react';
import { Dialog, Transition } from '@headlessui/react';
export default function Modal({ children, show = false, maxWidth = '2xl', closeable = true, onClose = () => {} }) {
const close = () => {
if (closeable) {
onClose();
}
};
const maxWidthClass = {
sm: 'sm:max-w-sm',
md: 'sm:max-w-md',
lg: 'sm:max-w-lg',
xl: 'sm:max-w-xl',
'2xl': 'sm:max-w-2xl',
}[maxWidth];
return (
<Transition show={show} as={Fragment} leave="duration-200">
<Dialog
as="div"
id="modal"
className="fixed inset-0 flex overflow-y-auto px-4 py-6 sm:px-0 items-center z-50 transform transition-all"
onClose={close}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute inset-0 bg-gray-500/75" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel
className={`mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full sm:mx-auto ${maxWidthClass}`}
>
{children}
</Dialog.Panel>
</Transition.Child>
</Dialog>
</Transition>
);
}

View File

@@ -0,0 +1,18 @@
import { Link } from '@inertiajs/react';
export default function NavLink({ active = false, className = '', children, ...props }) {
return (
<Link
{...props}
className={
'inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 transition duration-150 ease-in-out focus:outline-none ' +
(active
? 'border-indigo-400 text-gray-900 focus:border-indigo-700 '
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300 ') +
className
}
>
{children}
</Link>
);
}

View File

@@ -0,0 +1,15 @@
export default function PrimaryButton({ className = '', disabled, children, ...props }) {
return (
<button
{...props}
className={
`inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150 ${
disabled && 'opacity-25'
} ` + className
}
disabled={disabled}
>
{children}
</button>
);
}

View File

@@ -0,0 +1,88 @@
import Input from '@/Components/Input'
import TextArea from '@/Components/TextArea'
import Button from '@/Components/Button'
import ImageInput from '@/Components/ImageInput'
import SelectInput from '@/Components/SelectInput'
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function ProductForm({ onCancel, onSubmit, categories, preselectedCategory=null }) {
categories = categories.map((category) => ({value: category.id, label: category.title}));
let [title, setTitle] = useState();
let [description, setDescription] = useState();
let [price, setPrice] = useState(0);
let [inventory, setInventory] = useState(-1);
let [selectedImage, setSelectedImage] = useState(null);
let [createCategoryOpen, setCreateCategoryOpen] = useState(false);
let [selectedCategory, setSelectedCategory] = useState(preselectedCategory ?? (categories[0] ?? null));
function handleSubmit(){
let data = {
title: title,
description: description,
price: price,
inventory: inventory,
image: selectedImage,
}
console.log(selectedCategory);
router.post('/products/store/'+String(selectedCategory.value), data);
onCancel();
}
function handleCreateCategoryOpen(inputValue){
setCategoryTitle(inputValue);
setCreateCategoryOpen(true);
}
function handleModalCancel(){
setCreateCategoryOpen(false);
setCreateTagOpen(false);
}
function handleCategoryModalSubmit(){
setCreateCategoryOpen(false);
setData('category', {value: categoryTitle, label: categoryTitle});
}
return (
<div className="flex flex-col bg-white rounded-lg w-full max-w-4xl h-fit p-8 space-y-8">
<span className="pb-4 text-lg font-semibold">محصول جدید</span>
<SelectInput value={ selectedCategory } setValue={e => setSelectedCategory(e) } options={ categories } onCreateOption={ handleCreateCategoryOpen } label="دسته بندی" className="w-full"/>
<div className="flex flex-col lg:flex-row">
<div className="w-full pl-4 flex flex-col space-y-4">
<Input label="عنوان" className="w-full ml-6" onChange={(e) => {setTitle(e.target.value)}} />
<div className="flex justify-between">
<Input label="قیمت" className="w-full ml-6" onChange={(e) => {setPrice(e.target.value)}} />
<Input label="موجودی" className="w-full" onChange={(e) => {setInventory(e.target.value)}} />
</div>
<TextArea label="توضیحات (اختیاری)" className="" onChange={(e) => {setDescription(e.target.value)}}/>
</div>
<div className="w-full pr-4 h-full">
<ImageInput id="image" image={selectedImage && (
<div className="flex flex-col justify-center w-full h-52 rounded-2xl">
<img
alt="not found"
className="h-52 object-scale-down"
src={URL.createObjectURL(selectedImage)}
/>
<button class="z-10" onClick={() => setSelectedImage(null)}>حذف</button>
</div>
)} onChange={(event) => {
setSelectedImage(event.target.files[0]);
}} label="تصویر" />
</div>
</div>
<div className="flex">
<Button onClick={ onCancel } className="bg-white text-black border-gray-300 border-2 w-full transition hover:bg-zinc-100 ml-4 shadow-none">لغو</Button>
<Button onClick={ handleSubmit } className="w-full text-white bg-zinc-800 transition hover:bg-zinc-700">ایجاد</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
import { Link } from '@inertiajs/react'
import { useState, useEffect } from 'react'
import { router } from '@inertiajs/react'
export default function ProductTable({ items, title, links}) {
console.log(items);
const items_list = items.map(items => {
return (
<tr key={items.id} className="border-y text-right font-normal">
<td className="py-2">{ items.id }</td>
<td className="py-2">{ items.images[0] ? <img className="z-0 w-16 h-16 rounded-lg shadow-inner p-1" src={ items.images[0].thumbnail } alt={ items.images[0].alt } /> : null }</td>
<td className="py-2 text-lg">{ items.title }</td>
<td className="py-2">{ items.price }</td>
<td className="py-2">{ items.inventory }</td>
</tr>
)}
);
return (
<div className="h-full">
<div className="flex flex-col bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition overflow-auto max-h-full">
<div className="w-full px-3">
<table className="table-auto border-collapse w-full text-right">
<th className="text-sm font-normal w-12">
<span>
آیدی
</span>
</th>
<th className="text-sm font-normal w-20">
<span>
عکس
</span>
</th>
<th className="text-sm font-normal">
<span>
عنوان
</span>
</th>
<th className="text-sm font-normal">
<span>
قیمت
</span>
</th>
<th className="text-sm font-normal">
<span>
موجودی
</span>
</th>
{items_list}
</table>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,16 @@
import { Link } from '@inertiajs/react';
export default function ResponsiveNavLink({ active = false, className = '', children, ...props }) {
return (
<Link
{...props}
className={`w-full flex items-start pl-3 pr-4 py-2 border-l-4 ${
active
? 'border-indigo-400 text-indigo-700 bg-indigo-50 focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700'
: 'border-transparent text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300'
} text-base font-medium focus:outline-none transition duration-150 ease-in-out ${className}`}
>
{children}
</Link>
);
}

View File

@@ -0,0 +1,12 @@
export default function Searchbar({ onSearch, value }) {
return (
<div class="relative h-fit">
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
</div>
<input type="search" id="default-search" class="block w-52 p-2 pr-10 pl-2 rounded-lg border-gray-200 focus:border-red-200 focus:ring-red-200" placeholder="جستجو..." />
</div>
);
}

View File

@@ -0,0 +1,16 @@
export default function SecondaryButton({ type = 'button', className = '', disabled, children, ...props }) {
return (
<button
{...props}
type={type}
className={
`inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150 ${
disabled && 'opacity-25'
} ` + className
}
disabled={disabled}
>
{children}
</button>
);
}

View File

@@ -0,0 +1,13 @@
import CreatableSelect from 'react-select/creatable';
import { useState } from 'react'
export default function Input({ options, value, setValue, onCreateOption, id, label, className, placeholder=null, isMulti=false}) {
return (
<div className={"relative flex flex-col" + " " + className}>
<label className="pr-3 pb-0.5">{ label }</label>
<CreatableSelect isClearable value={ value } onChange={(newValue) => setValue(newValue)} onCreateOption={(e) => onCreateOption(e) } formatCreateLabel={(inputValue) => "ایجاد " + label + ' "' + inputValue + '"'} placeholder={placeholder} options={options} isMulti={isMulti}/>
</div>
);
}

View File

@@ -0,0 +1,17 @@
import SidebarItem from '@/Components/SidebarItem'
export default function Sidebar() {
return (
<div className="flex flex-col w-60 h-full bg-white">
<div className="p-6 border-b text-2xl">
<span>پنل مدیریت</span>
</div>
<div>
<SidebarItem title="دشبورد" href="/dashboard" />
<SidebarItem title="محصولات" href="/products" />
<SidebarItem title="دسته بندی ها" href="/categories" />
<SidebarItem title="مشخصه ها" href="/properties" />
</div>
</div>
)
}

View File

@@ -0,0 +1,8 @@
import { Link } from '@inertiajs/react'
export default function SidebarItem({ title, href }) {
return (
<div className="flex border-b hover:border-l hover:border-l-black transition duration-600 hover:bg-gradient-to-r from-gray-200 to-gray-50">
<Link className="w-full h-full p-4" href={href}>{title}</Link>
</div>
)
}

View File

@@ -0,0 +1,8 @@
export default function TextArea({ onChange, id, label, className, placeholder=null }) {
return (
<div className={"flex flex-col" + " " + className}>
<label className="pr-3 pb-0.5">{ label }</label>
<textarea onChange={ onChange } id={id} type="text" placeholder={placeholder} className="w-full border-gray-300 rounded-lg"></textarea>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import { forwardRef, useEffect, useRef } from 'react';
export default forwardRef(function TextInput({ type = 'text', className = '', isFocused = false, ...props }, ref) {
const input = ref ? ref : useRef();
useEffect(() => {
if (isFocused) {
input.current.focus();
}
}, []);
return (
<input
{...props}
type={type}
className={
'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm ' +
className
}
ref={input}
/>
);
});

View File

@@ -0,0 +1,125 @@
import { useState } from 'react';
import ApplicationLogo from '@/Components/ApplicationLogo';
import Dropdown from '@/Components/Dropdown';
import NavLink from '@/Components/NavLink';
import ResponsiveNavLink from '@/Components/ResponsiveNavLink';
import { Link } from '@inertiajs/react';
export default function Authenticated({ user, header, children }) {
const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
return (
<div className="min-h-screen bg-gray-100">
<nav className="bg-white border-b border-gray-100">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex">
<div className="shrink-0 flex items-center">
<Link href="/">
<ApplicationLogo className="block h-9 w-auto fill-current text-gray-800" />
</Link>
</div>
<div className="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<NavLink href={route('dashboard')} active={route().current('dashboard')}>
Dashboard
</NavLink>
</div>
</div>
<div className="hidden sm:flex sm:items-center sm:ml-6">
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<span className="inline-flex rounded-md">
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150"
>
{user.name}
<svg
className="ml-2 -mr-0.5 h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</span>
</Dropdown.Trigger>
<Dropdown.Content>
<Dropdown.Link href={route('profile.edit')}>Profile</Dropdown.Link>
<Dropdown.Link href={route('logout')} method="post" as="button">
Log Out
</Dropdown.Link>
</Dropdown.Content>
</Dropdown>
</div>
</div>
<div className="-mr-2 flex items-center sm:hidden">
<button
onClick={() => setShowingNavigationDropdown((previousState) => !previousState)}
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out"
>
<svg className="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
<path
className={!showingNavigationDropdown ? 'inline-flex' : 'hidden'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
<path
className={showingNavigationDropdown ? 'inline-flex' : 'hidden'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<div className={(showingNavigationDropdown ? 'block' : 'hidden') + ' sm:hidden'}>
<div className="pt-2 pb-3 space-y-1">
<ResponsiveNavLink href={route('dashboard')} active={route().current('dashboard')}>
Dashboard
</ResponsiveNavLink>
</div>
<div className="pt-4 pb-1 border-t border-gray-200">
<div className="px-4">
<div className="font-medium text-base text-gray-800">{user.name}</div>
<div className="font-medium text-sm text-gray-500">{user.email}</div>
</div>
<div className="mt-3 space-y-1">
<ResponsiveNavLink href={route('profile.edit')}>Profile</ResponsiveNavLink>
<ResponsiveNavLink method="post" href={route('logout')} as="button">
Log Out
</ResponsiveNavLink>
</div>
</div>
</div>
</nav>
{header && (
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">{header}</div>
</header>
)}
<main>{children}</main>
</div>
);
}

View File

@@ -0,0 +1,18 @@
import ApplicationLogo from '@/Components/ApplicationLogo';
import { Link } from '@inertiajs/react';
export default function Guest({ children }) {
return (
<div className="min-h-screen flex flex-col sm:justify-center items-center pt-6 sm:pt-0 bg-gray-100">
<div>
<Link href="/">
<ApplicationLogo className="w-20 h-20 fill-current text-gray-500" />
</Link>
</div>
<div className="w-full sm:max-w-md mt-6 px-6 py-4 bg-white shadow-md overflow-hidden sm:rounded-lg">
{children}
</div>
</div>
);
}

View File

@@ -0,0 +1,26 @@
import Header from '@/Components/Header'
import Sidebar from '@/Components/Sidebar'
import { router } from '@inertiajs/react'
import { useEffect } from 'react';
export default function Layout({ children }) {
useEffect(() => {
const interval = setInterval(() => {
router.reload();
}, 30000);
return () => clearInterval(interval);
}, []);
return (
<div dir="rtl" className="w-screen h-screen flex flex-row bg-gray-200">
<div className="h-full w-fit hidden md:flex">
<Sidebar />
</div>
<div className="flex flex-col space-y-12 h-full w-full p-8 px-12 overflow-auto" >
<Header />
{children}
</div>
</div>
);
}

View File

@@ -0,0 +1,59 @@
import { useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Head, useForm } from '@inertiajs/react';
export default function ConfirmPassword() {
const { data, setData, post, processing, errors, reset } = useForm({
password: '',
});
useEffect(() => {
return () => {
reset('password');
};
}, []);
const submit = (e) => {
e.preventDefault();
post(route('password.confirm'));
};
return (
<GuestLayout>
<Head title="Confirm Password" />
<div className="mb-4 text-sm text-gray-600">
This is a secure area of the application. Please confirm your password before continuing.
</div>
<form onSubmit={submit}>
<div className="mt-4">
<InputLabel htmlFor="password" value="Password" />
<TextInput
id="password"
type="password"
name="password"
value={data.password}
className="mt-1 block w-full"
isFocused={true}
onChange={(e) => setData('password', e.target.value)}
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div className="flex items-center justify-end mt-4">
<PrimaryButton className="ml-4" disabled={processing}>
Confirm
</PrimaryButton>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,50 @@
import GuestLayout from '@/Layouts/GuestLayout';
import InputError from '@/Components/InputError';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Head, useForm } from '@inertiajs/react';
export default function ForgotPassword({ status }) {
const { data, setData, post, processing, errors } = useForm({
email: '',
});
const submit = (e) => {
e.preventDefault();
post(route('password.email'));
};
return (
<GuestLayout>
<Head title="Forgot Password" />
<div className="mb-4 text-sm text-gray-600">
Forgot your password? No problem. Just let us know your email address and we will email you a password
reset link that will allow you to choose a new one.
</div>
{status && <div className="mb-4 font-medium text-sm text-green-600">{status}</div>}
<form onSubmit={submit}>
<TextInput
id="email"
type="email"
name="email"
value={data.email}
className="mt-1 block w-full"
isFocused={true}
onChange={(e) => setData('email', e.target.value)}
/>
<InputError message={errors.email} className="mt-2" />
<div className="flex items-center justify-end mt-4">
<PrimaryButton className="ml-4" disabled={processing}>
Email Password Reset Link
</PrimaryButton>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,97 @@
import { useEffect } from 'react';
import Checkbox from '@/Components/Checkbox';
import GuestLayout from '@/Layouts/GuestLayout';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Head, Link, useForm } from '@inertiajs/react';
export default function Login({ status, canResetPassword }) {
const { data, setData, post, processing, errors, reset } = useForm({
email: '',
password: '',
remember: false,
});
useEffect(() => {
return () => {
reset('password');
};
}, []);
const submit = (e) => {
e.preventDefault();
post(route('login'));
};
return (
<GuestLayout>
<Head title="Log in" />
{status && <div className="mb-4 font-medium text-sm text-green-600">{status}</div>}
<form onSubmit={submit}>
<div>
<InputLabel htmlFor="email" value="Email" />
<TextInput
id="email"
type="email"
name="email"
value={data.email}
className="mt-1 block w-full"
autoComplete="username"
isFocused={true}
onChange={(e) => setData('email', e.target.value)}
/>
<InputError message={errors.email} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="password" value="Password" />
<TextInput
id="password"
type="password"
name="password"
value={data.password}
className="mt-1 block w-full"
autoComplete="current-password"
onChange={(e) => setData('password', e.target.value)}
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div className="block mt-4">
<label className="flex items-center">
<Checkbox
name="remember"
checked={data.remember}
onChange={(e) => setData('remember', e.target.checked)}
/>
<span className="ml-2 text-sm text-gray-600">Remember me</span>
</label>
</div>
<div className="flex items-center justify-end mt-4">
{canResetPassword && (
<Link
href={route('password.request')}
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Forgot your password?
</Link>
)}
<PrimaryButton className="ml-4" disabled={processing}>
Log in
</PrimaryButton>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,117 @@
import { useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Head, Link, useForm } from '@inertiajs/react';
export default function Register() {
const { data, setData, post, processing, errors, reset } = useForm({
name: '',
email: '',
password: '',
password_confirmation: '',
});
useEffect(() => {
return () => {
reset('password', 'password_confirmation');
};
}, []);
const submit = (e) => {
e.preventDefault();
post(route('register'));
};
return (
<GuestLayout>
<Head title="Register" />
<form onSubmit={submit}>
<div>
<InputLabel htmlFor="name" value="Name" />
<TextInput
id="name"
name="name"
value={data.name}
className="mt-1 block w-full"
autoComplete="name"
isFocused={true}
onChange={(e) => setData('name', e.target.value)}
required
/>
<InputError message={errors.name} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="email" value="Email" />
<TextInput
id="email"
type="email"
name="email"
value={data.email}
className="mt-1 block w-full"
autoComplete="username"
onChange={(e) => setData('email', e.target.value)}
required
/>
<InputError message={errors.email} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="password" value="Password" />
<TextInput
id="password"
type="password"
name="password"
value={data.password}
className="mt-1 block w-full"
autoComplete="new-password"
onChange={(e) => setData('password', e.target.value)}
required
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
<TextInput
id="password_confirmation"
type="password"
name="password_confirmation"
value={data.password_confirmation}
className="mt-1 block w-full"
autoComplete="new-password"
onChange={(e) => setData('password_confirmation', e.target.value)}
required
/>
<InputError message={errors.password_confirmation} className="mt-2" />
</div>
<div className="flex items-center justify-end mt-4">
<Link
href={route('login')}
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Already registered?
</Link>
<PrimaryButton className="ml-4" disabled={processing}>
Register
</PrimaryButton>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,90 @@
import { useEffect } from 'react';
import GuestLayout from '@/Layouts/GuestLayout';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Head, useForm } from '@inertiajs/react';
export default function ResetPassword({ token, email }) {
const { data, setData, post, processing, errors, reset } = useForm({
token: token,
email: email,
password: '',
password_confirmation: '',
});
useEffect(() => {
return () => {
reset('password', 'password_confirmation');
};
}, []);
const submit = (e) => {
e.preventDefault();
post(route('password.store'));
};
return (
<GuestLayout>
<Head title="Reset Password" />
<form onSubmit={submit}>
<div>
<InputLabel htmlFor="email" value="Email" />
<TextInput
id="email"
type="email"
name="email"
value={data.email}
className="mt-1 block w-full"
autoComplete="username"
onChange={(e) => setData('email', e.target.value)}
/>
<InputError message={errors.email} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="password" value="Password" />
<TextInput
id="password"
type="password"
name="password"
value={data.password}
className="mt-1 block w-full"
autoComplete="new-password"
isFocused={true}
onChange={(e) => setData('password', e.target.value)}
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div className="mt-4">
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
<TextInput
type="password"
name="password_confirmation"
value={data.password_confirmation}
className="mt-1 block w-full"
autoComplete="new-password"
onChange={(e) => setData('password_confirmation', e.target.value)}
/>
<InputError message={errors.password_confirmation} className="mt-2" />
</div>
<div className="flex items-center justify-end mt-4">
<PrimaryButton className="ml-4" disabled={processing}>
Reset Password
</PrimaryButton>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,45 @@
import GuestLayout from '@/Layouts/GuestLayout';
import PrimaryButton from '@/Components/PrimaryButton';
import { Head, Link, useForm } from '@inertiajs/react';
export default function VerifyEmail({ status }) {
const { post, processing } = useForm({});
const submit = (e) => {
e.preventDefault();
post(route('verification.send'));
};
return (
<GuestLayout>
<Head title="Email Verification" />
<div className="mb-4 text-sm text-gray-600">
Thanks for signing up! Before getting started, could you verify your email address by clicking on the
link we just emailed to you? If you didn't receive the email, we will gladly send you another.
</div>
{status === 'verification-link-sent' && (
<div className="mb-4 font-medium text-sm text-green-600">
A new verification link has been sent to the email address you provided during registration.
</div>
)}
<form onSubmit={submit}>
<div className="mt-4 flex items-center justify-between">
<PrimaryButton disabled={processing}>Resend Verification Email</PrimaryButton>
<Link
href={route('logout')}
method="post"
as="button"
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Log Out
</Link>
</div>
</form>
</GuestLayout>
);
}

View File

@@ -0,0 +1,46 @@
import Layout from '@/Layouts/Layout';
import Searchbar from '@/Components/Searchbar';
import CreateButton from '@/Components/CreateButton';
import CategoryForm from '@/Components/CategoryForm';
import CategoryCard from '@/Components/CategoryCard';
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function Index({ categories }){
let [createOpen, setCreateOpen] = useState(false);
function handleCancel(){
setCreateOpen(false);
}
function handleCreateClick(){
setCreateOpen(true);
}
function handleCreate(){
setCreateOpen(false);
router.refresh();
}
const category_list = categories.map((category) => (
<CategoryCard category={ category } />
));
return (
<div className="relative flex flex-col h-full w-full space-y-6">
{createOpen ? <div className="flex items-center justify-center absolute top-0 z-50 backdrop-blur h-full w-full rounded-lg backdrop-brightness-75">
<CategoryForm onCancel = {handleCancel}/>
</div> : ''}
<div className="flex justify-between h-fit">
<Searchbar />
<CreateButton onClick={ handleCreateClick } title="افزودن دسته بندی"/>
</div>
<div className="grid grid-cols-2 gap-8 ">
{category_list}
</div>
</div>
);
}
Index.layout = page => <Layout children={page} />

View File

@@ -0,0 +1,19 @@
import Layout from '@/Layouts/Layout';
import Searchbar from '@/Components/Searchbar';
import CreateButton from '@/Components/CreateButton';
import ProductForm from '@/Components/ProductForm';
import ProductTable from '@/Components/ProductTable';
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function Index({ contents }){
return (
<div className="relative flex flex-col h-full w-full space-y-6">
hello
</div>
);
}
Index.layout = page => <Layout children={page} />

View File

@@ -0,0 +1,13 @@
import Layout from '@/Layouts/Layout';
export default function Dashboard({ data, visits }) {
return (
<div className="grid grid-cols-12 grid-rows-12 gap-6 h-full w-full">
Hello
</div>
);
}
Dashboard.layout = page => <Layout children={page} />

View File

@@ -0,0 +1,53 @@
import Layout from '@/Layouts/Layout';
import Searchbar from '@/Components/Searchbar';
import CreateButton from '@/Components/CreateButton';
import ProductForm from '@/Components/ProductForm';
import ProductTable from '@/Components/ProductTable';
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function Index({ products, links, categories }){
let [createOpen, setCreateOpen] = useState(false);
function handleCancel(){
setCreateOpen(false);
}
function handleCreateClick(){
setCreateOpen(true);
}
function handleCreate(){
setCreateOpen(false);
router.refresh();
}
const product_list = products.map((product) => (
<div className="flex flex-col items-center justify-center bg-white rounded-lg w-full h-full">
{product.images[0] ? <img src={ product.images[0].thumbnail } alt={ product.images[0].alt } className="h-52 w-full object-cover rounded-t-lg shadow-md" /> : null}
<div className="p-4 py-6 flex flex-col items-center justify-center space-y-4">
<span className="text-xl">{product.title}</span>
<span className="text-gray-600 text-center">{product.description}</span>
</div>
</div>
));
return (
<div className="relative flex flex-col h-full w-full space-y-6">
{createOpen ? <div className="flex items-center justify-center absolute top-0 z-10 backdrop-blur h-full w-full rounded-lg backdrop-brightness-75">
<ProductForm onCancel = {handleCancel} categories={ categories }/>
</div> : ''}
<div className="flex justify-between h-fit">
<Searchbar />
<CreateButton onClick={ handleCreateClick } title="افزودن محصول"/>
</div>
<div className="grid grid-cols-4 gap-8">
{product_list}
</div>
<ProductTable items={ products } links={ links } title="محصولات" />
</div>
);
}
Index.layout = page => <Layout children={page} />

View File

@@ -0,0 +1,36 @@
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import DeleteUserForm from './Partials/DeleteUserForm';
import UpdatePasswordForm from './Partials/UpdatePasswordForm';
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
import { Head } from '@inertiajs/react';
export default function Edit({ auth, mustVerifyEmail, status }) {
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Profile</h2>}
>
<Head title="Profile" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<UpdateProfileInformationForm
mustVerifyEmail={mustVerifyEmail}
status={status}
className="max-w-xl"
/>
</div>
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<UpdatePasswordForm className="max-w-xl" />
</div>
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<DeleteUserForm className="max-w-xl" />
</div>
</div>
</div>
</AuthenticatedLayout>
);
}

View File

@@ -0,0 +1,99 @@
import { useRef, useState } from 'react';
import DangerButton from '@/Components/DangerButton';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import Modal from '@/Components/Modal';
import SecondaryButton from '@/Components/SecondaryButton';
import TextInput from '@/Components/TextInput';
import { useForm } from '@inertiajs/react';
export default function DeleteUserForm({ className = '' }) {
const [confirmingUserDeletion, setConfirmingUserDeletion] = useState(false);
const passwordInput = useRef();
const {
data,
setData,
delete: destroy,
processing,
reset,
errors,
} = useForm({
password: '',
});
const confirmUserDeletion = () => {
setConfirmingUserDeletion(true);
};
const deleteUser = (e) => {
e.preventDefault();
destroy(route('profile.destroy'), {
preserveScroll: true,
onSuccess: () => closeModal(),
onError: () => passwordInput.current.focus(),
onFinish: () => reset(),
});
};
const closeModal = () => {
setConfirmingUserDeletion(false);
reset();
};
return (
<section className={`space-y-6 ${className}`}>
<header>
<h2 className="text-lg font-medium text-gray-900">Delete Account</h2>
<p className="mt-1 text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Before
deleting your account, please download any data or information that you wish to retain.
</p>
</header>
<DangerButton onClick={confirmUserDeletion}>Delete Account</DangerButton>
<Modal show={confirmingUserDeletion} onClose={closeModal}>
<form onSubmit={deleteUser} className="p-6">
<h2 className="text-lg font-medium text-gray-900">
Are you sure you want to delete your account?
</h2>
<p className="mt-1 text-sm text-gray-600">
Once your account is deleted, all of its resources and data will be permanently deleted. Please
enter your password to confirm you would like to permanently delete your account.
</p>
<div className="mt-6">
<InputLabel htmlFor="password" value="Password" className="sr-only" />
<TextInput
id="password"
type="password"
name="password"
ref={passwordInput}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
className="mt-1 block w-3/4"
isFocused
placeholder="Password"
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div className="mt-6 flex justify-end">
<SecondaryButton onClick={closeModal}>Cancel</SecondaryButton>
<DangerButton className="ml-3" disabled={processing}>
Delete Account
</DangerButton>
</div>
</form>
</Modal>
</section>
);
}

View File

@@ -0,0 +1,113 @@
import { useRef } from 'react';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { useForm } from '@inertiajs/react';
import { Transition } from '@headlessui/react';
export default function UpdatePasswordForm({ className = '' }) {
const passwordInput = useRef();
const currentPasswordInput = useRef();
const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
current_password: '',
password: '',
password_confirmation: '',
});
const updatePassword = (e) => {
e.preventDefault();
put(route('password.update'), {
preserveScroll: true,
onSuccess: () => reset(),
onError: (errors) => {
if (errors.password) {
reset('password', 'password_confirmation');
passwordInput.current.focus();
}
if (errors.current_password) {
reset('current_password');
currentPasswordInput.current.focus();
}
},
});
};
return (
<section className={className}>
<header>
<h2 className="text-lg font-medium text-gray-900">Update Password</h2>
<p className="mt-1 text-sm text-gray-600">
Ensure your account is using a long, random password to stay secure.
</p>
</header>
<form onSubmit={updatePassword} className="mt-6 space-y-6">
<div>
<InputLabel htmlFor="current_password" value="Current Password" />
<TextInput
id="current_password"
ref={currentPasswordInput}
value={data.current_password}
onChange={(e) => setData('current_password', e.target.value)}
type="password"
className="mt-1 block w-full"
autoComplete="current-password"
/>
<InputError message={errors.current_password} className="mt-2" />
</div>
<div>
<InputLabel htmlFor="password" value="New Password" />
<TextInput
id="password"
ref={passwordInput}
value={data.password}
onChange={(e) => setData('password', e.target.value)}
type="password"
className="mt-1 block w-full"
autoComplete="new-password"
/>
<InputError message={errors.password} className="mt-2" />
</div>
<div>
<InputLabel htmlFor="password_confirmation" value="Confirm Password" />
<TextInput
id="password_confirmation"
value={data.password_confirmation}
onChange={(e) => setData('password_confirmation', e.target.value)}
type="password"
className="mt-1 block w-full"
autoComplete="new-password"
/>
<InputError message={errors.password_confirmation} className="mt-2" />
</div>
<div className="flex items-center gap-4">
<PrimaryButton disabled={processing}>Save</PrimaryButton>
<Transition
show={recentlySuccessful}
enter="transition ease-in-out"
enterFrom="opacity-0"
leave="transition ease-in-out"
leaveTo="opacity-0"
>
<p className="text-sm text-gray-600">Saved.</p>
</Transition>
</div>
</form>
</section>
);
}

View File

@@ -0,0 +1,103 @@
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
import { Link, useForm, usePage } from '@inertiajs/react';
import { Transition } from '@headlessui/react';
export default function UpdateProfileInformation({ mustVerifyEmail, status, className = '' }) {
const user = usePage().props.auth.user;
const { data, setData, patch, errors, processing, recentlySuccessful } = useForm({
name: user.name,
email: user.email,
});
const submit = (e) => {
e.preventDefault();
patch(route('profile.update'));
};
return (
<section className={className}>
<header>
<h2 className="text-lg font-medium text-gray-900">Profile Information</h2>
<p className="mt-1 text-sm text-gray-600">
Update your account's profile information and email address.
</p>
</header>
<form onSubmit={submit} className="mt-6 space-y-6">
<div>
<InputLabel htmlFor="name" value="Name" />
<TextInput
id="name"
className="mt-1 block w-full"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
required
isFocused
autoComplete="name"
/>
<InputError className="mt-2" message={errors.name} />
</div>
<div>
<InputLabel htmlFor="email" value="Email" />
<TextInput
id="email"
type="email"
className="mt-1 block w-full"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
required
autoComplete="username"
/>
<InputError className="mt-2" message={errors.email} />
</div>
{mustVerifyEmail && user.email_verified_at === null && (
<div>
<p className="text-sm mt-2 text-gray-800">
Your email address is unverified.
<Link
href={route('verification.send')}
method="post"
as="button"
className="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Click here to re-send the verification email.
</Link>
</p>
{status === 'verification-link-sent' && (
<div className="mt-2 font-medium text-sm text-green-600">
A new verification link has been sent to your email address.
</div>
)}
</div>
)}
<div className="flex items-center gap-4">
<PrimaryButton disabled={processing}>Save</PrimaryButton>
<Transition
show={recentlySuccessful}
enter="transition ease-in-out"
enterFrom="opacity-0"
leave="transition ease-in-out"
leaveTo="opacity-0"
>
<p className="text-sm text-gray-600">Saved.</p>
</Transition>
</div>
</form>
</section>
);
}

View File

@@ -0,0 +1,52 @@
import Layout from '@/Layouts/Layout';
import Searchbar from '@/Components/Searchbar';
import CreateButton from '@/Components/CreateButton';
import { useState } from 'react'
import { router } from '@inertiajs/react'
export default function Index({ products, links, categories }){
let [createOpen, setCreateOpen] = useState(false);
function handleCancel(){
setCreateOpen(false);
}
function handleCreateClick(){
setCreateOpen(true);
}
function handleCreate(){
setCreateOpen(false);
router.refresh();
}
const product_list = products.map((product) => (
<div className="flex flex-col items-center justify-center bg-white rounded-lg w-full h-full">
{product.images[0] ? <img src={ product.images[0].thumbnail } alt={ product.images[0].alt } className="h-52 w-full object-cover rounded-t-lg shadow-md" /> : null}
<div className="p-4 py-6 flex flex-col items-center justify-center space-y-4">
<span className="text-xl">{product.title}</span>
<span className="text-gray-600 text-center">{product.description}</span>
</div>
</div>
));
return (
<div className="relative flex flex-col h-full w-full space-y-6">
{createOpen ? <div className="flex items-center justify-center absolute top-0 z-10 backdrop-blur h-full w-full rounded-lg backdrop-brightness-75">
<ProductForm onCancel = {handleCancel} categories={ categories }/>
</div> : ''}
<div className="flex justify-between h-fit">
<Searchbar />
<CreateButton onClick={ handleCreateClick } title="افزودن محصول"/>
</div>
<div className="grid grid-cols-4 gap-8">
{product_list}
</div>
<ProductTable items={ products } links={ links } title="محصولات" />
</div>
);
}
Index.layout = page => <Layout children={page} />

26
resources/js/app.jsx Normal file
View File

@@ -0,0 +1,26 @@
import './bootstrap';
import '../css/app.css';
import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import.meta.glob([
'../images/**',
'../fonts/**',
]);
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')),
setup({ el, App, props }) {
const root = createRoot(el);
root.render(<App {...props} />);
},
progress: {
color: '#4B5563',
},
});

32
resources/js/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,32 @@
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// import Pusher from 'pusher-js';
// window.Pusher = Pusher;
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: import.meta.env.VITE_PUSHER_APP_KEY,
// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
// enabledTransports: ['ws', 'wss'],
// });