Init(Core): add to repo and add seeders
This commit is contained in:
7
resources/js/Components/ApplicationLogo.jsx
Normal file
7
resources/js/Components/ApplicationLogo.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
resources/js/Components/Button.jsx
Normal file
5
resources/js/Components/Button.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
10
resources/js/Components/CategoryAddItemButton.jsx
Normal file
10
resources/js/Components/CategoryAddItemButton.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
resources/js/Components/CategoryCard.jsx
Normal file
22
resources/js/Components/CategoryCard.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
38
resources/js/Components/CategoryForm.jsx
Normal file
38
resources/js/Components/CategoryForm.jsx
Normal 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");
|
||||
}
|
||||
9
resources/js/Components/CategoryItemCard.jsx
Normal file
9
resources/js/Components/CategoryItemCard.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
12
resources/js/Components/Checkbox.jsx
Normal file
12
resources/js/Components/Checkbox.jsx
Normal 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
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
11
resources/js/Components/CreateButton.jsx
Normal file
11
resources/js/Components/CreateButton.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
15
resources/js/Components/DangerButton.jsx
Normal file
15
resources/js/Components/DangerButton.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
91
resources/js/Components/Dropdown.jsx
Normal file
91
resources/js/Components/Dropdown.jsx
Normal 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;
|
||||
7
resources/js/Components/Header.jsx
Normal file
7
resources/js/Components/Header.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function Header(){
|
||||
return (
|
||||
<div className="bg-white h-16 w-full rounded-lg">
|
||||
Hello
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
resources/js/Components/ImageInput.jsx
Normal file
22
resources/js/Components/ImageInput.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
9
resources/js/Components/Input.jsx
Normal file
9
resources/js/Components/Input.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
resources/js/Components/InputError.jsx
Normal file
7
resources/js/Components/InputError.jsx
Normal 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;
|
||||
}
|
||||
7
resources/js/Components/InputLabel.jsx
Normal file
7
resources/js/Components/InputLabel.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
57
resources/js/Components/Modal.jsx
Normal file
57
resources/js/Components/Modal.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
18
resources/js/Components/NavLink.jsx
Normal file
18
resources/js/Components/NavLink.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
15
resources/js/Components/PrimaryButton.jsx
Normal file
15
resources/js/Components/PrimaryButton.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
88
resources/js/Components/ProductForm.jsx
Normal file
88
resources/js/Components/ProductForm.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
57
resources/js/Components/ProductTable.jsx
Normal file
57
resources/js/Components/ProductTable.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
16
resources/js/Components/ResponsiveNavLink.jsx
Normal file
16
resources/js/Components/ResponsiveNavLink.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
12
resources/js/Components/Searchbar.jsx
Normal file
12
resources/js/Components/Searchbar.jsx
Normal 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>
|
||||
);
|
||||
|
||||
}
|
||||
16
resources/js/Components/SecondaryButton.jsx
Normal file
16
resources/js/Components/SecondaryButton.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
13
resources/js/Components/SelectInput.jsx
Normal file
13
resources/js/Components/SelectInput.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
17
resources/js/Components/Sidebar.jsx
Normal file
17
resources/js/Components/Sidebar.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
resources/js/Components/SidebarItem.jsx
Normal file
8
resources/js/Components/SidebarItem.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
8
resources/js/Components/TextArea.jsx
Normal file
8
resources/js/Components/TextArea.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
23
resources/js/Components/TextInput.jsx
Normal file
23
resources/js/Components/TextInput.jsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
125
resources/js/Layouts/AuthenticatedLayout.jsx
Normal file
125
resources/js/Layouts/AuthenticatedLayout.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
18
resources/js/Layouts/GuestLayout.jsx
Normal file
18
resources/js/Layouts/GuestLayout.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
26
resources/js/Layouts/Layout.jsx
Normal file
26
resources/js/Layouts/Layout.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
59
resources/js/Pages/Auth/ConfirmPassword.jsx
Normal file
59
resources/js/Pages/Auth/ConfirmPassword.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
50
resources/js/Pages/Auth/ForgotPassword.jsx
Normal file
50
resources/js/Pages/Auth/ForgotPassword.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
97
resources/js/Pages/Auth/Login.jsx
Normal file
97
resources/js/Pages/Auth/Login.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
117
resources/js/Pages/Auth/Register.jsx
Normal file
117
resources/js/Pages/Auth/Register.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
90
resources/js/Pages/Auth/ResetPassword.jsx
Normal file
90
resources/js/Pages/Auth/ResetPassword.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
45
resources/js/Pages/Auth/VerifyEmail.jsx
Normal file
45
resources/js/Pages/Auth/VerifyEmail.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
46
resources/js/Pages/Categories/Index.jsx
Normal file
46
resources/js/Pages/Categories/Index.jsx
Normal 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} />
|
||||
19
resources/js/Pages/Content/Index.jsx
Normal file
19
resources/js/Pages/Content/Index.jsx
Normal 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} />
|
||||
13
resources/js/Pages/Dashboard.jsx
Normal file
13
resources/js/Pages/Dashboard.jsx
Normal 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} />
|
||||
53
resources/js/Pages/Products/Index.jsx
Normal file
53
resources/js/Pages/Products/Index.jsx
Normal 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} />
|
||||
36
resources/js/Pages/Profile/Edit.jsx
Normal file
36
resources/js/Pages/Profile/Edit.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
99
resources/js/Pages/Profile/Partials/DeleteUserForm.jsx
Normal file
99
resources/js/Pages/Profile/Partials/DeleteUserForm.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
113
resources/js/Pages/Profile/Partials/UpdatePasswordForm.jsx
Normal file
113
resources/js/Pages/Profile/Partials/UpdatePasswordForm.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
52
resources/js/Pages/Properties/Index.jsx
Normal file
52
resources/js/Pages/Properties/Index.jsx
Normal 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
26
resources/js/app.jsx
Normal 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
32
resources/js/bootstrap.js
vendored
Normal 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'],
|
||||
// });
|
||||
Reference in New Issue
Block a user