EJ-Dannug
4 months ago
27 changed files with 3498 additions and 32 deletions
-
80app/Http/Controllers/CategoryController.php
-
80app/Http/Controllers/FrameworkController.php
-
85app/Http/Controllers/InstructionController.php
-
86app/Http/Controllers/PageController.php
-
21app/Models/Category.php
-
21app/Models/Framework.php
-
43app/Models/Instruction.php
-
42app/Models/Page.php
-
1database/migrations/2024_06_09_184105_create_roles_table.php
-
29database/migrations/2024_06_27_124215_create_categories_table.php
-
29database/migrations/2024_06_27_202837_create_frameworks_table.php
-
32database/migrations/2024_06_29_044406_create_pages_table.php
-
33database/migrations/2024_06_29_215447_create_instructions_table.php
-
2024package-lock.json
-
12package.json
-
4resources/js/Components/DashboardComponents/ContentStatistics.tsx
-
3resources/js/Components/DashboardComponents/PagesOverview.tsx
-
17resources/js/Components/ErrorButton.tsx
-
2resources/js/Components/FormGroup.tsx
-
17resources/js/Components/SuccessButton.tsx
-
196resources/js/Pages/AppFramework/AddModal.tsx
-
2resources/js/Pages/Auth/Login.tsx
-
2resources/js/Pages/Auth/Register.tsx
-
285resources/js/Pages/RepositoryPage/Create.tsx
-
170resources/js/Pages/RepositoryPage/InstructionsForm.tsx
-
189resources/js/Pages/SubjectCategory/AddModal.tsx
-
25routes/auth.php
@ -0,0 +1,80 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Category; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\JsonResponse; |
|||
|
|||
class CategoryController extends Controller |
|||
{ |
|||
/** |
|||
* Display a listing of the resource. |
|||
*/ |
|||
public function index(): JsonResponse |
|||
{ |
|||
$categories = Category::all(); |
|||
return response()->json($categories); |
|||
} |
|||
|
|||
/** |
|||
* Show the form for creating a new resource. |
|||
*/ |
|||
public function create() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Store a newly created resource in storage. |
|||
*/ |
|||
public function store(Request $request): JsonResponse |
|||
{ |
|||
$request->validate([ |
|||
'subjectTitle' => 'required|string|max:255|unique:categories,subjectTitle', |
|||
'description' => 'nullable|string', |
|||
]); |
|||
|
|||
$category = Category::create([ |
|||
'subject_title' => $request->subjectTitle, |
|||
'description' => $request->description, |
|||
]); |
|||
|
|||
return response()->json([ |
|||
'message' => 'Subject category added!', |
|||
'category' => $category, |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Display the specified resource. |
|||
*/ |
|||
public function show(Category $category): JsonResponse |
|||
{ |
|||
return response()->json($category); |
|||
} |
|||
|
|||
/** |
|||
* Show the form for editing the specified resource. |
|||
*/ |
|||
public function edit(Category $category) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Update the specified resource in storage. |
|||
*/ |
|||
public function update(Request $request, Category $category) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Remove the specified resource from storage. |
|||
*/ |
|||
public function destroy(Category $category) |
|||
{ |
|||
//
|
|||
} |
|||
} |
@ -0,0 +1,80 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Framework; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\JsonResponse; |
|||
|
|||
class FrameworkController extends Controller |
|||
{ |
|||
/** |
|||
* Display a listing of the resource. |
|||
*/ |
|||
public function index(): JsonResponse |
|||
{ |
|||
$categories = Framework::all(); |
|||
return response()->json($categories); |
|||
} |
|||
|
|||
/** |
|||
* Show the form for creating a new resource. |
|||
*/ |
|||
public function create() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Store a newly created resource in storage. |
|||
*/ |
|||
public function store(Request $request): JsonResponse |
|||
{ |
|||
$request->validate([ |
|||
'frameworkName' => 'required|string|max:255', |
|||
'version' => 'nullable|string', |
|||
]); |
|||
|
|||
$framework = Framework::create([ |
|||
'framework_name' => $request->frameworkName, |
|||
'version' => $request->version, |
|||
]); |
|||
|
|||
return response()->json([ |
|||
'message' => 'Application framework added!', |
|||
'framework' => $framework, |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Display the specified resource. |
|||
*/ |
|||
public function show(Framework $framework) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Show the form for editing the specified resource. |
|||
*/ |
|||
public function edit(Framework $framework) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Update the specified resource in storage. |
|||
*/ |
|||
public function update(Request $request, Framework $framework) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Remove the specified resource from storage. |
|||
*/ |
|||
public function destroy(Framework $framework) |
|||
{ |
|||
//
|
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Instruction; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Http\JsonResponse; |
|||
|
|||
class InstructionController extends Controller |
|||
{ |
|||
/** |
|||
* Display a listing of the resource. |
|||
*/ |
|||
public function index(): JsonResponse |
|||
{ |
|||
|
|||
return response()->json(); |
|||
} |
|||
|
|||
/** |
|||
* Show the form for creating a new resource. |
|||
*/ |
|||
public function create() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Store a newly created resource in storage. |
|||
*/ |
|||
public function store(Request $request): JsonResponse |
|||
{ |
|||
$request->validate([ |
|||
'file' => 'required|file|mimes:json', |
|||
'frameworkID' => 'required|exists:frameworks,id', |
|||
'pageID' => 'required|exists:pages,id', |
|||
]); |
|||
|
|||
$file = $request->file('file'); |
|||
$path = $file->store('instructions', 'public'); |
|||
|
|||
$instruction = Instruction::create([ |
|||
'page_id' => $request->pageID, |
|||
'framework_id' => $request->frameworkID, |
|||
'file_path' => $path, |
|||
]); |
|||
|
|||
return response()->json([ |
|||
'message' => 'Instruction saved successfully', |
|||
'instruction' => $instruction, |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Display the specified resource. |
|||
*/ |
|||
public function show(Instruction $instruction) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Show the form for editing the specified resource. |
|||
*/ |
|||
public function edit(Instruction $instruction) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Update the specified resource in storage. |
|||
*/ |
|||
public function update(Request $request, Instruction $instruction) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Remove the specified resource from storage. |
|||
*/ |
|||
public function destroy(Instruction $instruction) |
|||
{ |
|||
//
|
|||
} |
|||
} |
@ -0,0 +1,86 @@ |
|||
<?php |
|||
|
|||
namespace App\Http\Controllers; |
|||
|
|||
use App\Models\Page; |
|||
use Inertia\Inertia; |
|||
use Inertia\Response; |
|||
use Illuminate\Http\Request; |
|||
use Illuminate\Contracts\Auth\MustVerifyEmail; |
|||
|
|||
class PageController extends Controller |
|||
{ |
|||
/** |
|||
* Display a listing of the resource. |
|||
*/ |
|||
public function index() |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Show the form for creating a new resource. |
|||
*/ |
|||
public function create(Request $request): Response |
|||
{ |
|||
return Inertia::render('RepositoryPage/Create', [ |
|||
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail, |
|||
'status' => session('status'), |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Store a newly created resource in storage. |
|||
*/ |
|||
public function store(Request $request) |
|||
{ |
|||
$request->validate([ |
|||
'pageTitle' => 'required|string|max:255', |
|||
'category' => 'required|exists:categories,id', |
|||
'introduction' => 'required|string', |
|||
]); |
|||
|
|||
$page = Page::create([ |
|||
'page_title' => $request->pageTitle, |
|||
'category_id' => $request->category, |
|||
'introduction' => $request->introduction, |
|||
]); |
|||
|
|||
return response()->json([ |
|||
'message' => 'Page created successfully!', |
|||
'page' => $page, |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* Display the specified resource. |
|||
*/ |
|||
public function show(Page $page) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Show the form for editing the specified resource. |
|||
*/ |
|||
public function edit(Page $page) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Update the specified resource in storage. |
|||
*/ |
|||
public function update(Request $request, Page $page) |
|||
{ |
|||
//
|
|||
} |
|||
|
|||
/** |
|||
* Remove the specified resource from storage. |
|||
*/ |
|||
public function destroy(Page $page) |
|||
{ |
|||
//
|
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Category extends Model |
|||
{ |
|||
use HasFactory; |
|||
|
|||
/** |
|||
* The attributes that are mass assignable. |
|||
* |
|||
* @var array<int, string> |
|||
*/ |
|||
protected $fillable = [ |
|||
'subjectTitle', |
|||
'description', |
|||
]; |
|||
} |
@ -0,0 +1,21 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Framework extends Model |
|||
{ |
|||
use HasFactory; |
|||
|
|||
/** |
|||
* The attributes that are mass assignable. |
|||
* |
|||
* @var array<int, string> |
|||
*/ |
|||
protected $fillable = [ |
|||
'frameworkName', |
|||
'version', |
|||
]; |
|||
} |
@ -0,0 +1,43 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Instruction extends Model |
|||
{ |
|||
use HasFactory; |
|||
|
|||
/** |
|||
* The attributes that are mass assignable. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $fillable = [ |
|||
'page_id', |
|||
'framework_id', |
|||
'file_path', |
|||
]; |
|||
|
|||
/** |
|||
* Get the attributes that should be cast. |
|||
* |
|||
* @return array<string, string> |
|||
*/ |
|||
protected function casts(): array |
|||
{ |
|||
return [ |
|||
'page_id' => 'integer', |
|||
'framework_id' => 'integer', |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* Get the framework associated with the instruction. |
|||
*/ |
|||
public function framework() |
|||
{ |
|||
return $this->belongsTo(Framework::class); |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
<?php |
|||
|
|||
namespace App\Models; |
|||
|
|||
use Illuminate\Database\Eloquent\Factories\HasFactory; |
|||
use Illuminate\Database\Eloquent\Model; |
|||
|
|||
class Page extends Model |
|||
{ |
|||
use HasFactory; |
|||
|
|||
/** |
|||
* The attributes that are mass assignable. |
|||
* |
|||
* @var array |
|||
*/ |
|||
protected $fillable = [ |
|||
'page_title', |
|||
'introduction', |
|||
'category_id', |
|||
]; |
|||
|
|||
/** |
|||
* Get the attributes that should be cast. |
|||
* |
|||
* @return array<string, string> |
|||
*/ |
|||
protected function casts(): array |
|||
{ |
|||
return [ |
|||
'category_id' => 'integer', |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* Get the framework associated with the instruction. |
|||
*/ |
|||
public function category() |
|||
{ |
|||
return $this->belongsTo(Category::class); |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Support\Facades\Schema; |
|||
|
|||
return new class extends Migration |
|||
{ |
|||
/** |
|||
* Run the migrations. |
|||
*/ |
|||
public function up(): void |
|||
{ |
|||
Schema::create('categories', function (Blueprint $table) { |
|||
$table->id(); |
|||
$table->string('subject_title'); |
|||
$table->text('description')->nullable(); |
|||
$table->timestamps(); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
*/ |
|||
public function down(): void |
|||
{ |
|||
Schema::dropIfExists('categories'); |
|||
} |
|||
}; |
@ -0,0 +1,29 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Support\Facades\Schema; |
|||
|
|||
return new class extends Migration |
|||
{ |
|||
/** |
|||
* Run the migrations. |
|||
*/ |
|||
public function up(): void |
|||
{ |
|||
Schema::create('frameworks', function (Blueprint $table) { |
|||
$table->id(); |
|||
$table->string('framework_name'); |
|||
$table->string('version'); |
|||
$table->timestamps(); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
*/ |
|||
public function down(): void |
|||
{ |
|||
Schema::dropIfExists('frameworks'); |
|||
} |
|||
}; |
@ -0,0 +1,32 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Support\Facades\Schema; |
|||
|
|||
return new class extends Migration |
|||
{ |
|||
/** |
|||
* Run the migrations. |
|||
*/ |
|||
public function up(): void |
|||
{ |
|||
Schema::create('pages', function (Blueprint $table) { |
|||
$table->id(); |
|||
$table->string('page_title'); |
|||
$table->text('introduction'); |
|||
$table->unsignedBigInteger('category_id')->nullable(); |
|||
$table->timestamps(); |
|||
|
|||
$table->foreign('category_id')->references('id')->on('categories')->onDelete('set null'); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
*/ |
|||
public function down(): void |
|||
{ |
|||
Schema::dropIfExists('pages'); |
|||
} |
|||
}; |
@ -0,0 +1,33 @@ |
|||
<?php |
|||
|
|||
use Illuminate\Database\Migrations\Migration; |
|||
use Illuminate\Database\Schema\Blueprint; |
|||
use Illuminate\Support\Facades\Schema; |
|||
|
|||
return new class extends Migration |
|||
{ |
|||
/** |
|||
* Run the migrations. |
|||
*/ |
|||
public function up(): void |
|||
{ |
|||
Schema::create('instructions', function (Blueprint $table) { |
|||
$table->id(); |
|||
$table->unsignedBigInteger('page_id'); |
|||
$table->unsignedBigInteger('framework_id'); |
|||
$table->string('file_path'); |
|||
$table->timestamps(); |
|||
|
|||
$table->foreign('page_id')->references('id')->on('pages')->onDelete('cascade'); |
|||
$table->foreign('framework_id')->references('id')->on('frameworks')->onDelete('cascade'); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Reverse the migrations. |
|||
*/ |
|||
public function down(): void |
|||
{ |
|||
Schema::dropIfExists('instructions'); |
|||
} |
|||
}; |
2024
package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,17 @@ |
|||
import { ButtonHTMLAttributes } from 'react'; |
|||
|
|||
export default function ErrorButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) { |
|||
return ( |
|||
<button |
|||
{...props} |
|||
className={ |
|||
`flex items-center btn bg-error-main outline-none text-white hover:bg-error-hover active:bg-error-pressed ${ |
|||
disabled && 'opacity-25' |
|||
} ` + className
|
|||
} |
|||
disabled={disabled} |
|||
> |
|||
{children} |
|||
</button> |
|||
); |
|||
} |
@ -0,0 +1,17 @@ |
|||
import { ButtonHTMLAttributes } from 'react'; |
|||
|
|||
export default function SuccessButton({ className = '', disabled, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) { |
|||
return ( |
|||
<button |
|||
{...props} |
|||
className={ |
|||
`flex items-center btn bg-success-main outline-none text-white hover:bg-success-hover active:bg-success-pressed ${ |
|||
disabled && 'opacity-25' |
|||
} ` + className
|
|||
} |
|||
disabled={disabled} |
|||
> |
|||
{children} |
|||
</button> |
|||
); |
|||
} |
@ -0,0 +1,196 @@ |
|||
import { FormEventHandler, Fragment, PropsWithChildren, useEffect, useState } from 'react'; |
|||
import { Dialog, Transition } from '@headlessui/react'; |
|||
import { FileText, Box, XCircle, ArrowRightCircle, CheckCircle, XOctagon, Code } from 'react-feather'; |
|||
import { useForm } from '@inertiajs/react'; |
|||
import FormGroup from '@/Components/FormGroup'; |
|||
import InputLabel from '@/Components/InputLabel'; |
|||
import TextInput from '@/Components/TextInput'; |
|||
import InputError from '@/Components/InputError'; |
|||
import ErrorButton from '@/Components/ErrorButton'; |
|||
import PrimaryButton from '@/Components/PrimaryButton'; |
|||
import axios from 'axios'; |
|||
import Modal from '@/Components/Modal'; |
|||
import ModalButton from '@/Components/ModalButton'; |
|||
|
|||
export default function AddFrameworkModal({ |
|||
show = false, |
|||
maxWidth = '2xl', |
|||
closeable = true, |
|||
onClose = () => {}, |
|||
onFrameworkAdded = () => {}, |
|||
className = '', |
|||
}: PropsWithChildren<{ |
|||
show: boolean; |
|||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'; |
|||
closeable?: boolean; |
|||
onClose: CallableFunction; |
|||
onFrameworkAdded: CallableFunction; |
|||
className?: string; |
|||
}>) { |
|||
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]; |
|||
|
|||
const { data, setData, reset, errors } = useForm({ |
|||
frameworkName: '', |
|||
version:'', |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
return () => { |
|||
reset('frameworkName'); |
|||
reset('version'); |
|||
}; |
|||
}, []); |
|||
|
|||
const [status, setStatus] = useState(''); |
|||
const [feedback, setFeedback] = useState(''); |
|||
const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false); |
|||
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); |
|||
|
|||
const handleCloseSuccessModal = () => { |
|||
setIsSuccessModalOpen(false); |
|||
onFrameworkAdded(); |
|||
close(); |
|||
} |
|||
|
|||
const submit: FormEventHandler = async (e) => { |
|||
e.preventDefault(); |
|||
|
|||
try { |
|||
const response = await axios.post(route('framework.add'), data); |
|||
setStatus('Success') |
|||
setFeedback(response.data.message); |
|||
setIsSuccessModalOpen(true); |
|||
} catch (error) { |
|||
setStatus('Error') |
|||
if (axios.isAxiosError(error)) { |
|||
setFeedback(error.response?.data.message || 'An error occurred'); |
|||
} else { |
|||
setFeedback('An unexpected error occurred'); |
|||
} |
|||
setIsErrorModalOpen(true); |
|||
} |
|||
}; |
|||
|
|||
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-neutral-0 opacity-50" /> |
|||
</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 border-4 rounded-lg overflow-hidden shadow-xl items-center transform transition-all sm:w-full sm:mx-auto bg-neutral-white ${maxWidthClass} ${className}`} |
|||
> |
|||
<div className="p-4 flex flex-col"> |
|||
<h1 className="font-bold text-xl text-primary-main flex items-center my-2"><FileText className="stroke-primary-main mr-2" />Add New Framework</h1> |
|||
<div className="divider"></div> |
|||
|
|||
<form id="AddFrameworkForm"> |
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="frameworkName"><Box className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="frameworkName" className="mx-2 font-semibold">App Framework Name:</label> |
|||
|
|||
</FormGroup> |
|||
<TextInput |
|||
id='frameworkName' |
|||
name='frameworkName' |
|||
value={data.frameworkName} |
|||
autoComplete='off' |
|||
required |
|||
onChange={(e) => setData('frameworkName', e.target.value)} |
|||
/> |
|||
<InputError message={errors.frameworkName} className="mt-2" /> |
|||
</div> |
|||
|
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="version"><Code className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="version" className="mx-2 font-semibold">Version:</label> |
|||
|
|||
</FormGroup> |
|||
<TextInput |
|||
id='version' |
|||
name='version' |
|||
value={data.version} |
|||
autoComplete='off' |
|||
required |
|||
onChange={(e) => setData('version', e.target.value)} |
|||
/> |
|||
<InputError message={errors.version} className="mt-2" /> |
|||
</div> |
|||
|
|||
<div className='flex items-center justify-center mt-3 w-full'> |
|||
<ErrorButton type="button" className='w-1/2' onClick={close}> |
|||
<XCircle className='stroke-neutral-10' /> |
|||
Cancel |
|||
</ErrorButton> |
|||
<PrimaryButton type="button" className='w-1/2' onClick={submit}> |
|||
<ArrowRightCircle className='stroke-neutral-10' /> |
|||
Save |
|||
</PrimaryButton> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
<Modal show={isSuccessModalOpen} onClose={() => setIsSuccessModalOpen(false)} maxWidth="lg" styling='success'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<CheckCircle className='stroke-success-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={handleCloseSuccessModal} className="bg-success-main text-white hover:bg-success-hover active:bg-success-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
|
|||
<Modal show={isErrorModalOpen} onClose={() => setIsErrorModalOpen(false)} maxWidth="lg" styling='error'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<XOctagon className='stroke-error-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={() => setIsErrorModalOpen(false)} className="bg-error-main text-white hover:bg-error-hover active:bg-error-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
</Dialog.Panel> |
|||
</Transition.Child> |
|||
</Dialog> |
|||
</Transition> |
|||
); |
|||
} |
@ -0,0 +1,285 @@ |
|||
import FormGroup from "@/Components/FormGroup"; |
|||
import InputError from "@/Components/InputError"; |
|||
import InputLabel from "@/Components/InputLabel"; |
|||
import TextInput from "@/Components/TextInput"; |
|||
import Authenticated from "@/Layouts/AuthenticatedLayout"; |
|||
import { PageProps } from "@/types"; |
|||
import { Head, Link, useForm } from "@inertiajs/react"; |
|||
import { Book, Box, CheckCircle, FileText, Paperclip, Plus, PlusCircle, XCircle, XOctagon } from "react-feather"; |
|||
import 'codemirror/lib/codemirror.css'; |
|||
import 'codemirror/mode/javascript/javascript'; |
|||
import PrimaryButton from "@/Components/PrimaryButton"; |
|||
import Instructions from "./InstructionsForm"; |
|||
import Modal from "@/Components/Modal"; |
|||
import ModalButton from "@/Components/ModalButton"; |
|||
import { FormEventHandler, useEffect, useState } from "react"; |
|||
import AddCategoryModal from "../SubjectCategory/AddModal"; |
|||
import axios from "axios"; |
|||
import SuccessButton from "@/Components/SuccessButton"; |
|||
import ErrorButton from "@/Components/ErrorButton"; |
|||
import dayjs from "dayjs"; |
|||
|
|||
interface Category { |
|||
id: number; |
|||
subject_title: string; |
|||
description?: string; |
|||
} |
|||
|
|||
export default function Create({ auth }: PageProps) { |
|||
const thisUser = auth.user; |
|||
|
|||
const { data, setData, processing, errors } = useForm({ |
|||
pageTitle: '', |
|||
category: '', |
|||
introduction:'', |
|||
}); |
|||
|
|||
const [isAddCategoryModalOpen, setIsAddCategoryModalOpen] = useState(false); |
|||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { |
|||
const selectedValue = e.target.value; |
|||
setData('category', selectedValue); |
|||
if (selectedValue === "0") { |
|||
setIsAddCategoryModalOpen(true); |
|||
} |
|||
} |
|||
|
|||
const [status, setStatus] = useState(''); |
|||
const [feedback, setFeedback] = useState(''); |
|||
const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false); |
|||
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); |
|||
const [categories, setCategories] = useState<Category[]>([]); |
|||
|
|||
const fetchCategories = async () => { |
|||
try { |
|||
const response = await axios.get<Category[]>(route('categories.index')); |
|||
setCategories(response.data); |
|||
} catch (e) { |
|||
setStatus('Function Failure'); |
|||
setFeedback('Failed to fetch categories'); |
|||
setIsErrorModalOpen(true); |
|||
} |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
if (thisUser.role_id !== 1) { |
|||
setStatus('Warning'); |
|||
setFeedback('You are unauthorized to access this page'); |
|||
setIsErrorModalOpen(true); |
|||
} else { |
|||
fetchCategories(); |
|||
} |
|||
}, [thisUser.role_id]); |
|||
|
|||
const handleCloseSuccessModal = () => { |
|||
setIsSuccessModalOpen(false); |
|||
window.location.href = '/dashboard'; |
|||
} |
|||
|
|||
const handleCloseErrorModal = () => { |
|||
setIsErrorModalOpen(false); |
|||
window.location.href = '/dashboard'; |
|||
}; |
|||
|
|||
const [instructions, setInstructions] = useState<{ id: number; steps: any[]; frameworkID: string; }[]>([]); |
|||
const [nextId, setNextId] = useState<number>(0); |
|||
const handleAddInstruction = () => { |
|||
setInstructions([...instructions, { id: nextId, steps: [], frameworkID: '' }]); |
|||
setNextId(nextId + 1); |
|||
}; |
|||
const handleRemoveInstruction = (id: number) => { |
|||
setInstructions(prevInstructions => |
|||
prevInstructions.filter(instruction => instruction.id !== id) |
|||
); |
|||
}; |
|||
const handleUpdateInstruction = (id: number, steps: any[], frameworkID: string) => { |
|||
setInstructions(prevInstructions => |
|||
prevInstructions.map(instruction => |
|||
instruction.id === id ? { ...instruction, steps, frameworkID } : instruction |
|||
) |
|||
); |
|||
}; |
|||
|
|||
const submit: FormEventHandler = async (e) => { |
|||
e.preventDefault(); |
|||
|
|||
if (data.category==="0" || data.category==="") { |
|||
alert('Please choose a valid category.') |
|||
return |
|||
} |
|||
|
|||
try { |
|||
const response = await axios.post(route('page.add'), data); |
|||
const pageID = response.data.page.id; |
|||
|
|||
await Promise.all(instructions.map(async (instruction) => { |
|||
if (instruction.frameworkID === "0" || instruction.frameworkID === "") { |
|||
alert('Please choose a valid framework for each instruction.'); |
|||
return; |
|||
} |
|||
|
|||
const timestamp = dayjs().format('YYYYMMDDHHmmss'); |
|||
const jsonString = JSON.stringify({ steps: instruction.steps }); |
|||
const blob = new Blob([jsonString], { type: 'application/json' }); |
|||
const file = new File([blob], `${timestamp}.json`, { type: 'application/json' }); |
|||
|
|||
const formData = new FormData(); |
|||
formData.append('file', file); |
|||
formData.append('frameworkID', instruction.frameworkID); |
|||
formData.append('pageID', pageID.toString()); |
|||
|
|||
await axios.post(route('instruction.add'), formData, { |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data', |
|||
}, |
|||
}); |
|||
})); |
|||
|
|||
setStatus('Success'); |
|||
setFeedback(response.data.message); |
|||
setIsSuccessModalOpen(true); |
|||
} catch (error) { |
|||
setStatus('Error') |
|||
if (axios.isAxiosError(error)) { |
|||
setFeedback(error.response?.data.message || 'An error occurred'); |
|||
} else { |
|||
setFeedback('An unexpected error occurred'); |
|||
} |
|||
setIsErrorModalOpen(true); |
|||
} |
|||
} |
|||
|
|||
const renderIfAdmin = () => { |
|||
if (thisUser.role_id === 1) { |
|||
return ( |
|||
<div> |
|||
<div className="breadcrumbs text-sm"> |
|||
<ul> |
|||
<li><Link href="/dashboard">Home</Link></li> |
|||
<li><a>Repository Pages</a></li> |
|||
<li>Add New Repository Page</li> |
|||
</ul> |
|||
</div> |
|||
|
|||
<h1 className="font-bold text-xl text-primary-main flex items-center my-2"><FileText className="stroke-primary-main mr-2" />Create Repository Page</h1> |
|||
<div className="divider"></div> |
|||
<form id="CreateRepositoryPageForm" onSubmit={submit}> |
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="pageTitle"><Box className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="pageTitle" className="mx-2 font-semibold">Page Title:</label> |
|||
|
|||
</FormGroup> |
|||
<TextInput |
|||
id='pageTitle' |
|||
name='pageTitle' |
|||
value={data.pageTitle} |
|||
autoComplete='off' |
|||
required |
|||
onChange={(e) => setData('pageTitle', e.target.value)} |
|||
/> |
|||
<InputError message={errors.pageTitle} className="mt-2" /> |
|||
</div> |
|||
|
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="category"><Book className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="category" className="mx-2 font-semibold">Subject Category:</label> |
|||
</FormGroup> |
|||
<select name="category" id="category" value={data.category} onChange={handleChange} |
|||
className='px-2 py-0 flex-grow w-full h-[30px] bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border focus:border-primary-hover'> |
|||
<option value="" selected disabled></option> |
|||
{categories.map(category => ( |
|||
<option key={category.id} value={category.id}> |
|||
{category.subject_title} |
|||
</option> |
|||
))} |
|||
<option value="0" className="text-secondary-main font-semibold">Add new framework</option> |
|||
</select> |
|||
</div> |
|||
|
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="description"><Paperclip className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="description" className="mx-2 font-semibold">Introduction:</label> |
|||
</FormGroup> |
|||
<textarea className='px-2 flex-grow w-full bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border-2 focus:border-primary-hover ' |
|||
onChange={(e) => setData('introduction', e.target.value)} /> |
|||
</div> |
|||
{instructions.map((instruction) => ( |
|||
<Instructions |
|||
key={instruction.id} |
|||
instruction_id={instruction.id} |
|||
onDelete={handleRemoveInstruction} |
|||
onUpdate={handleUpdateInstruction} |
|||
/> |
|||
))} |
|||
<PrimaryButton type='button' disabled={processing} onClick={handleAddInstruction} className="w-full"> |
|||
<Plus className='stroke-neutral-10' /> |
|||
Add Instruction |
|||
</PrimaryButton> |
|||
|
|||
<div className="divider"></div> |
|||
<div className="flex"> |
|||
<ErrorButton className="w-1/2" onClick={() => window.location.href = '/dashboard'}> |
|||
<XCircle className='stroke-neutral-10' /> |
|||
Cancel |
|||
</ErrorButton> |
|||
<SuccessButton type="submit" className="w-1/2"> |
|||
<PlusCircle className='stroke-neutral-10' /> |
|||
Save Repository Page |
|||
</SuccessButton> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<Authenticated user={thisUser}> |
|||
<Head title="Repository Page" /> |
|||
|
|||
<div id="CreateRepositoryPage" className="drawer lg:drawer-open"> |
|||
<input id="my-drawer-2" type="checkbox" className="drawer-toggle" /> |
|||
<div className="drawer-content flex flex-row justify-between p-6 bg-neutral-20"> |
|||
<div className="h-full w-full bg-neutral-10 shadow-md p-6 rounded-lg"> |
|||
{renderIfAdmin()} |
|||
</div> |
|||
</div> |
|||
<div className="drawer-side"> |
|||
<label htmlFor="my-drawer-2" aria-label="close sidebar" className="drawer-overlay"></label> |
|||
|
|||
<ul className="menu p-4 w-56 h-full bg-primary-background text-base-content"> |
|||
{/* Sidebar content here */} |
|||
<li><a>Sidebar Item 1</a></li> |
|||
<li><a>Sidebar Item 2</a></li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
|
|||
<AddCategoryModal show={isAddCategoryModalOpen} onClose={() => setIsAddCategoryModalOpen(false)} onCategoryAdded={fetchCategories} /> |
|||
|
|||
<Modal show={isSuccessModalOpen} onClose={() => setIsSuccessModalOpen(false)} maxWidth="lg" styling='success'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<CheckCircle className='stroke-success-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={handleCloseSuccessModal} className="bg-success-main text-white hover:bg-success-hover active:bg-success-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
|
|||
<Modal show={isErrorModalOpen} onClose={() => setIsErrorModalOpen(false)} maxWidth="lg" styling='error'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<XOctagon className='stroke-error-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={handleCloseErrorModal} className="bg-error-main text-white hover:bg-error-hover active:bg-error-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
</Authenticated> |
|||
); |
|||
} |
@ -0,0 +1,170 @@ |
|||
import { useEffect, useState } from 'react'; |
|||
import ReactQuill from 'react-quill'; |
|||
import 'react-quill/dist/quill.snow.css'; |
|||
import CodeMirror from '@uiw/react-codemirror'; |
|||
import { html } from '@codemirror/lang-html'; |
|||
import { javascript } from '@codemirror/lang-javascript'; |
|||
import { php } from '@codemirror/lang-php'; |
|||
import 'codemirror/lib/codemirror.css'; |
|||
import 'codemirror/theme/material.css'; |
|||
import PrimaryButton from '@/Components/PrimaryButton'; |
|||
import { Code, Download, Minus, MinusCircle, Plus } from 'react-feather'; |
|||
import FormGroup from '@/Components/FormGroup'; |
|||
import InputLabel from '@/Components/InputLabel'; |
|||
import TextInput from '@/Components/TextInput'; |
|||
import AddFrameworkModal from '../AppFramework/AddModal'; |
|||
import axios from 'axios'; |
|||
|
|||
interface InstructionsProps { |
|||
instruction_id: number; |
|||
onDelete: (instruction_id: number) => void; |
|||
onUpdate: (instruction_id: number, steps: any[], frameworkID: string) => void; |
|||
} |
|||
|
|||
interface Framework { |
|||
id: number; |
|||
framework_name: string; |
|||
version: string; |
|||
} |
|||
|
|||
export default function Instructions({ instruction_id, onDelete, onUpdate }: InstructionsProps) { |
|||
|
|||
const [frameworkID, setFrameworkID] = useState(''); |
|||
|
|||
const [frameworks, setFrameworks] = useState<Framework[]>([]); |
|||
const fetchFrameworks = async () => { |
|||
try { |
|||
const response = await axios.get<Framework[]>(route('frameworks.index')); |
|||
setFrameworks(response.data); |
|||
} catch (error) { |
|||
console.error('Failed to fetch frameworks', error); |
|||
} |
|||
}; |
|||
|
|||
useEffect(() => { |
|||
fetchFrameworks(); |
|||
}, []); |
|||
|
|||
const [isAddFrameworkModalOpen, setIsAddFrameworkModalOpen] = useState(false); |
|||
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { |
|||
const selectedValue = e.target.value; |
|||
setFrameworkID(selectedValue); |
|||
if (selectedValue === "0") { |
|||
setIsAddFrameworkModalOpen(true); |
|||
} |
|||
} |
|||
|
|||
const [steps, setSteps] = useState<Array<{ title: string, content: string, code: string }>>([]); |
|||
|
|||
const handleAddStep = () => { |
|||
setSteps([...steps, { title: '', content: '', code: '' }]); |
|||
}; |
|||
|
|||
const handleRemoveStep = (step_id: number) => { |
|||
setSteps(prevSteps => prevSteps.filter((_, index) => index !== step_id)); |
|||
}; |
|||
|
|||
const handleTitleChange = (step_id: number, title: string) => { |
|||
const newSteps = [...steps]; |
|||
newSteps[step_id].title = title; |
|||
setSteps(newSteps); |
|||
onUpdate(instruction_id, newSteps, frameworkID); |
|||
}; |
|||
|
|||
const handleContentChange = (step_id: number, content: string) => { |
|||
const newSteps = [...steps]; |
|||
newSteps[step_id].content = content; |
|||
setSteps(newSteps); |
|||
onUpdate(instruction_id, newSteps, frameworkID); |
|||
}; |
|||
|
|||
const handleCodeChange = (step_id: number, code: string) => { |
|||
const newSteps = [...steps]; |
|||
newSteps[step_id].code = code; |
|||
setSteps(newSteps); |
|||
onUpdate(instruction_id, newSteps, frameworkID); |
|||
}; |
|||
|
|||
return ( |
|||
<div className="h-full w-full bg-neutral-10 shadow-md my-4 p-6 rounded-lg flex flex-col items-start"> |
|||
<div className='flex w-full justify-between items-center'> |
|||
<h1 className="font-bold text-lg text-secondary-main flex items-center my-2">Instructions Editor</h1> |
|||
<button className="btn btn-link text-error-main p-0 no-underline" onClick={() => onDelete(instruction_id)}> |
|||
<Minus size={20} /> |
|||
Remove Instruction |
|||
</button> |
|||
</div> |
|||
|
|||
<div className='w-full'> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="frameworkID"><Code className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="frameworkID" className="ml-2 w-1/4">Application Framework:</label> |
|||
<select name="frameworkID" id="frameworkID" value={frameworkID} onChange={handleChange} |
|||
className='px-2 py-0 flex-grow w-full h-[30px] bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border focus:border-primary-hover'> |
|||
<option value="" selected disabled></option> |
|||
{frameworks.map(framework => ( |
|||
<option key={framework.id} value={framework.id}> |
|||
{framework.framework_name} |
|||
</option> |
|||
))} |
|||
<option value="0" className="text-secondary-main font-semibold">Add new framework</option> |
|||
</select> |
|||
</FormGroup> |
|||
</div> |
|||
|
|||
<div className='w-full my-4'> |
|||
{steps.map((step, step_id) => ( |
|||
<div key={step_id}> |
|||
<div className='flex justify-between items-center'> |
|||
<div className='flex w-full items-center'> |
|||
<h3 className='font-semibold text-lg w-1/7'>Step {step_id + 1}</h3> |
|||
<TextInput |
|||
id='stepTitle' |
|||
name='stepTitle' |
|||
value={step.title} |
|||
placeholder='Step Title' |
|||
autoComplete='off' |
|||
required |
|||
onChange={(e) => handleTitleChange(step_id, e.target.value)} |
|||
className='w-1/4 mx-4' |
|||
/> |
|||
</div> |
|||
<button type="button" className="btn btn-link text-error-main p-0 no-underline" onClick={() => handleRemoveStep(step_id)}> |
|||
<Minus size={20} /> |
|||
Remove Step |
|||
</button> |
|||
</div> |
|||
|
|||
<p>Textual Instruction:</p> |
|||
<ReactQuill |
|||
value={step.content} |
|||
onChange={(content) => handleContentChange(step_id, content)} |
|||
className="h-1/2" |
|||
modules={{ |
|||
toolbar: [ |
|||
['bold', 'italic', 'underline'], |
|||
[{'list': 'ordered'}, {'list': 'bullet'}], |
|||
['link', 'image', 'video'] |
|||
] |
|||
}} |
|||
/> |
|||
<p className='mt-2'>Code Snippet:</p> |
|||
<CodeMirror |
|||
value={step.code} |
|||
extensions={[php(), javascript(), html()]} |
|||
onChange={(value) => handleCodeChange(step_id, value)} |
|||
/> |
|||
<div className="divider divider-neutral-20"></div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
|
|||
<PrimaryButton type="button" onClick={handleAddStep} className='w-full'> |
|||
<Plus className='stroke-neutral-10' /> |
|||
Add Steps |
|||
</PrimaryButton> |
|||
|
|||
<AddFrameworkModal show={isAddFrameworkModalOpen} onClose={() => setIsAddFrameworkModalOpen(false)} onFrameworkAdded={fetchFrameworks} /> |
|||
</div> |
|||
); |
|||
}; |
@ -0,0 +1,189 @@ |
|||
import { FormEventHandler, Fragment, PropsWithChildren, useEffect, useState } from 'react'; |
|||
import { Dialog, Transition } from '@headlessui/react'; |
|||
import { FileText, Box, Info, XCircle, ArrowRightCircle, CheckCircle, XOctagon } from 'react-feather'; |
|||
import { useForm } from '@inertiajs/react'; |
|||
import FormGroup from '@/Components/FormGroup'; |
|||
import InputLabel from '@/Components/InputLabel'; |
|||
import TextInput from '@/Components/TextInput'; |
|||
import InputError from '@/Components/InputError'; |
|||
import ErrorButton from '@/Components/ErrorButton'; |
|||
import PrimaryButton from '@/Components/PrimaryButton'; |
|||
import axios from 'axios'; |
|||
import Modal from '@/Components/Modal'; |
|||
import ModalButton from '@/Components/ModalButton'; |
|||
|
|||
export default function AddCategoryModal({ |
|||
show = false, |
|||
maxWidth = '2xl', |
|||
closeable = true, |
|||
onClose = () => {}, |
|||
onCategoryAdded = () => {}, |
|||
className = '', |
|||
}: PropsWithChildren<{ |
|||
show: boolean; |
|||
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'; |
|||
closeable?: boolean; |
|||
onClose: CallableFunction; |
|||
onCategoryAdded: CallableFunction; |
|||
className?: string; |
|||
}>) { |
|||
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]; |
|||
|
|||
const { data, setData, errors, reset } = useForm({ |
|||
subjectTitle: '', |
|||
description:'', |
|||
}); |
|||
|
|||
useEffect(() => { |
|||
return () => { |
|||
reset('subjectTitle'); |
|||
reset('description'); |
|||
}; |
|||
}, []); |
|||
|
|||
const [status, setStatus] = useState(''); |
|||
const [feedback, setFeedback] = useState(''); |
|||
const [isSuccessModalOpen, setIsSuccessModalOpen] = useState(false); |
|||
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false); |
|||
|
|||
const handleCloseSuccessModal = () => { |
|||
setIsSuccessModalOpen(false); |
|||
onCategoryAdded(); |
|||
close(); |
|||
} |
|||
|
|||
const submit: FormEventHandler = async (e) => { |
|||
e.preventDefault(); |
|||
|
|||
try { |
|||
const response = await axios.post(route('category.add'), data); |
|||
setStatus('Success') |
|||
setFeedback(response.data.message); |
|||
setIsSuccessModalOpen(true); |
|||
} catch (error) { |
|||
setStatus('Error') |
|||
if (axios.isAxiosError(error)) { |
|||
setFeedback(error.response?.data.message || 'An error occurred'); |
|||
} else { |
|||
setFeedback('An unexpected error occurred'); |
|||
} |
|||
setIsErrorModalOpen(true); |
|||
} |
|||
}; |
|||
|
|||
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-neutral-0 opacity-50" /> |
|||
</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 border-4 rounded-lg overflow-hidden shadow-xl items-center transform transition-all sm:w-full sm:mx-auto bg-neutral-white ${maxWidthClass} ${className}`} |
|||
> |
|||
<div className="p-4 flex flex-col"> |
|||
<h1 className="font-bold text-xl text-primary-main flex items-center my-2"><FileText className="stroke-primary-main mr-2" />Add New Category</h1> |
|||
<div className="divider"></div> |
|||
|
|||
<form id="AddCategoryForm"> |
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="subjectTitle"><Box className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="subjectTitle" className="mx-2 font-semibold">Subject Category Title:</label> |
|||
|
|||
</FormGroup> |
|||
<TextInput |
|||
id='subjectTitle' |
|||
name='subjectTitle' |
|||
value={data.subjectTitle} |
|||
autoComplete='off' |
|||
required |
|||
onChange={(e) => setData('subjectTitle', e.target.value)} |
|||
/> |
|||
<InputError message={errors.subjectTitle} className="mt-2" /> |
|||
</div> |
|||
|
|||
<div> |
|||
<FormGroup> |
|||
<InputLabel htmlFor="category"><Info className='stroke-neutral-10' /></InputLabel> |
|||
<label htmlFor="category" className="mx-2 font-semibold">Description:</label> |
|||
</FormGroup> |
|||
<textarea className="px-2 flex-grow w-full bg-primary-background border border-primary-hover focus:outline-none focus:bg-neutral-10 focus:border-2 focus:border-primary-hover" |
|||
onChange={(e) => setData('description', e.target.value)}> |
|||
</textarea> |
|||
</div> |
|||
|
|||
<div className='flex items-center justify-center mt-3 w-full'> |
|||
<ErrorButton type="button" className='w-1/2' onClick={close}> |
|||
<XCircle className='stroke-neutral-10' /> |
|||
Cancel |
|||
</ErrorButton> |
|||
<PrimaryButton type="button" onClick={submit} className='w-1/2'> |
|||
<ArrowRightCircle className='stroke-neutral-10' /> |
|||
Save |
|||
</PrimaryButton> |
|||
</div> |
|||
</form> |
|||
</div> |
|||
|
|||
<Modal show={isSuccessModalOpen} onClose={() => setIsSuccessModalOpen(false)} maxWidth="lg" styling='success'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<CheckCircle className='stroke-success-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={handleCloseSuccessModal} className="bg-success-main text-white hover:bg-success-hover active:bg-success-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
|
|||
<Modal show={isErrorModalOpen} onClose={() => setIsErrorModalOpen(false)} maxWidth="lg" styling='error'> |
|||
<div className="p-4 flex flex-col items-center"> |
|||
<XOctagon className='stroke-error-main' size={80} /> |
|||
<h2 className="text-xl font-bold mt-2">{ status }</h2> |
|||
<p className="mt-4">{ feedback }</p> |
|||
<ModalButton onClick={() => setIsErrorModalOpen(false)} className="bg-error-main text-white hover:bg-error-hover active:bg-error-pressed"> |
|||
Close |
|||
</ModalButton> |
|||
</div> |
|||
</Modal> |
|||
</Dialog.Panel> |
|||
</Transition.Child> |
|||
</Dialog> |
|||
</Transition> |
|||
); |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue